Vue.js 源碼:https://github.com/vuejs/vuevue
Vue技術內幕:http://hcysun.me/vue-design/node
源碼分析文檔:https://ustbhuangyi.github.io/vue-analysis/react
一、知識點git
二、https://ustbhuangyi.github.io/vue-analysis/prepare/flow.htmlgithub
Flow 是facebook出品的JavaScript靜態類型檢查工具。Vue.js的源碼就利用Fow作了靜態類型檢測正則表達式
爲何用Flow?segmentfault
由於JavaScript是動態類型語言(弱類型語言),雖然靈活,可是也容易出現隱患代碼。數組
類型檢查是當前動態類型語言的發展趨勢,所謂類型檢查,就是在編譯期儘早發現(由類型錯誤引發的)bug,又不影響代碼運行(不須要運行時動態檢查類型),使編寫 JavaScript 具備和編寫 Java 等強類型語言相近的體驗。瀏覽器
Flow的工做方式
類型推斷:經過變量的使用上下文來推斷出變量類型,而後根據這些推斷來檢查類型。
1 /*@flow*/`` 2 3 function split(str) { 4 return str.split(' ') 5 } 6 7 split(11)
Flow 檢查上述代碼後會報錯,由於函數 split
期待的參數是字符串,而咱們輸入了數字。
類型註釋:事先註釋好咱們期待的類型,Flow 會基於這些註釋來判斷
1 /*@flow*/ 2 3 function add(x: number, y: number): number { 4 return x + y 5 } 6 7 add('Hello', 11)
如今 Flow 就能檢查出錯誤,由於函數參數的期待類型爲數字,而咱們提供了字符串。
Flow還支持一些常見的類型註釋:數組、類和對象、Null
三、Vue.js源碼目錄設計 https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html
Vue.js 的源碼都在 src 目錄下,其目錄結構以下:
1 src 2 ├── compiler # 編譯相關 3 ├── core # 核心代碼 4 ├── platforms # 不一樣平臺的支持 5 ├── server # 服務端渲染 6 ├── sfc # .vue 文件解析 7 ├── shared # 共享代碼
從 Vue.js 的目錄設計能夠看到,做者把功能模塊拆分的很是清楚,相關的邏輯放在一個獨立的目錄下維護,而且把複用的代碼也抽成一個獨立目錄。
這樣的目錄設計讓代碼的閱讀性和可維護性都變強,是很是值得學習和推敲的。
四、源碼構建 https://ustbhuangyi.github.io/vue-analysis/prepare/build.html#構建腳本
主要包括構建腳本和構建過程
五、new Vue的時候,會把傳入的option都綁定到$options上,因此咱們能夠經過vm.$options或者this.$options獲取傳入的參數(如:el,data)
六、new Vue()的時候發生了什麼?
地址:https://ustbhuangyi.github.io/vue-analysis/data-driven/new-vue.html
new
關鍵字在 Javascript 語言中表明實例化是一個對象,而 Vue
其實是一個類,類在 Javascript 中是用 Function 來實現的,來看一下源碼,在src/core/instance/index.js
中。
會調用_init函數進行初始化。會初始化生命週期、事件、props、data、methods、computed與watch等。
最主要的是經過Object.defineProperty設置setter與getter函數,用來實現【響應式】與【依賴收集】
七、Vue 實例掛載的實現
Vue 中咱們是經過 $mount
實例方法去掛載 vm
的
八、Vue 的 _render
方法是實例的一個私有方法,它用來把實例渲染成一個虛擬 Node。它的定義在 src/core/instance/render.js
文件中:
九、其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標籤名、數據、子節點、鍵值等,其它屬性都是都是用來擴展 VNode 的靈活性以及實現一些特殊 feature 的。因爲 VNode 只是用來映射到真實 DOM 的渲染,不須要包含操做 DOM 的方法,所以它是很是輕量和簡單的。
Virtual DOM 除了它的數據結構的定義,映射到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。那麼在 Vue.js 中,VNode 的 create 是經過以前提到的 createElement
方法建立的
十、Vue.js 利用 createElement 方法建立 VNode,它定義在 src/core/vdom/create-elemenet.js
中:
分爲倆部分:children 的規範化和VNode 的建立
那麼至此,咱們大體瞭解了 createElement
建立 VNode 的過程,每一個 VNode 有 children
,children
每一個元素也是一個 VNode,這樣就造成了一個 VNode Tree,它很好的描述了咱們的 DOM Tree。
十一、Vue 的 _update
是實例的一個私有方法,它被調用的時機有 2 個,一個是首次渲染,一個是數據更新的時候;因爲咱們這一章節只分析首次渲染部分,數據更新部分會在以後分析響應式原理的時候涉及。_update
方法的做用是把 VNode 渲染成真實的 DOM,它的定義在 src/core/instance/lifecycle.js
中:
十二、初始化 Vue 到最終渲染的整個過程。
從new Vue開始,先執行init,進行一堆初始化操做;而後進行掛載,執行$mount
$mount的執行過程:若是是帶編譯版本的(就是template這樣的,咱們通常寫的那種),就執行compile,生成render function;若是直接就是render function
獲得render function後,調用render方法,生成vnode,以後經過patch過程,生成真實的DOM
備註:在vue中,會把以前寫的真實的dom刪掉,而後經過Virtual DOM從新生成真實的dom
1三、
1四、
知道了 3 種異步組件的實現方式,而且看到高級異步組件的實現是很是巧妙的,它實現了 loading、resolve、reject、timeout 4 種狀態。異步組件實現的本質是 2 次渲染,除了 0 delay 的高級異步組件第一次直接渲染成 loading 組件外,其它都是第一次渲染生成一個註釋節點,當異步獲取組件成功後,再經過 forceRender
強制從新渲染,這樣就能正確渲染出咱們異步加載的組件了。
1五、
defineReactive
函數最開始初始化 Dep
對象的實例,接着拿到 obj
的屬性描述符,而後對子對象遞歸調用 observe
方法,這樣就保證了不管 obj
的結構多複雜,它的全部子屬性也能變成響應式的對象,這樣咱們訪問或修改 obj
中一個嵌套較深的屬性,也能觸發 getter 和 setter。
1六、 Vue 會把普通對象變成響應式對象,響應式對象 getter 相關的邏輯就是作依賴收集
Vue爲數據中的每個key維護一個訂閱者列表。對於生成的數據,經過Object.defineProperty
對其中的每個key進行處理,主要是爲每個key設置get
, set
方法,以此來爲對應的key收集訂閱者,並在值改變時通知對應的訂閱者。
最開始new Vue(),初始化代碼的時候,就會進行依賴收集,以後改變值之後,會再次進行依賴收集
參考網站:https://www.jianshu.com/p/e6e1fa824849
1七、計算屬性 computed的優化有倆處: 參考:https://ustbhuangyi.github.io/vue-analysis/reactive/computed-watcher.html#computed
(1)、當初始化computed的時候,會先進行收集依賴,但不會觸發watch,只有當渲染到computed計算的屬性時,就觸發了計算屬性的 getter
,它會拿到計算屬性對應的 watcher
,而後執行 watcher.depend()
(2)、一旦咱們對計算屬性依賴的數據作修改,則會觸發 setter 過程,通知全部訂閱它變化的 watcher
更新,執行 watcher.update()
方法
記住這裏:getAndInvoke
函數會從新計算,而後對比新舊值,若是變化了(就算依賴一直變化,只要最終值沒有,那就不會觸發回調函數)則執行回調函數,那麼這裏這個回調函數是 this.dep.notify()
,在咱們這個場景下就是觸發了渲染 watcher
從新渲染。
1八、
響應式原理圖
參考:https://segmentfault.com/q/1010000010977528
對上面的理解:頁面中所用到的數據data,渲染的時候就會觸發Object.defineReactive的getter方法
getter方法會將window.target(watch,每一個用到這個數據的地方,都會生成一個watch)保存到subs數組中
subs數組抽取出來,封裝到Dep類中,getter中要new Dep(),調用Dep類中的addSub,將watch保存到subs數組中
數據改變的時候,就會觸發setter方法==》觸發Dep類中的notify方法==》遍歷subs數組==》觸發watch中的update方法
綜合:每個使用的數據都有一個單獨的Dep類,每一個Dep類中有多個watch(根據使用此數據來生成,一個地方使用就生成一個watch)
1九、編譯入口邏輯之因此這麼繞,是由於 Vue.js 在不一樣的平臺下都會有編譯的過程,所以編譯過程當中的依賴的配置 baseOptions
會有所不一樣。而編譯過程會屢次執行,但這同一個平臺下每一次的編譯過程配置又是相同的,爲了避免讓這些配置在每次編譯過程都經過參數傳入,Vue.js 利用了函數柯里化的技巧很好的實現了 baseOptions
的參數保留。一樣,Vue.js 也是利用函數柯里化技巧把基礎的編譯過程函數抽出來,經過 createCompilerCreator(baseCompile)
的方式把真正編譯的過程和其它邏輯如對編譯配置處理、緩存處理等剝離開,這樣的設計仍是很是巧妙的。
parse
的目標是把 template
模板字符串轉換成 AST 樹,它是一種用 JavaScript 對象的形式來描述整個模板。那麼整個 parse
的過程是利用正則表達式順序解析模板,當解析到開始標籤、閉合標籤、文本的時候都會分別執行對應的回調函數,來達到構造 AST 樹的目的。
AST 元素節點總共有 3 種類型,type
爲 1 表示是普通元素,爲 2 表示是表達式,爲 3 表示是純文本。
optimize
的過程,就是深度遍歷這個 AST 樹,去檢測它的每一顆子樹是否是靜態節點,若是是靜態節點則它們生成 DOM 永遠不須要改變,這對運行時對模板的更新起到極大的優化做用。
咱們經過 optimize
咱們把整個 AST 樹中的每個 AST 元素節點標記了 static
和 staticRoot
,它會影響咱們接下來執行代碼生成的過程。
20、event事件
注意:vm.$emit
是給當前的 vm
上派發的實例,之因此咱們經常使用它作父子組件通信,是由於它的回調函數的定義是在父組件中,對於咱們這個例子而言,當子組件的 button
被點擊了,它經過 this.$emit('select')
派發事件,那麼子組件的實例就監聽到了這個 select
事件,並執行它的回調函數——定義在父組件中的 selectHandler
方法,這樣就至關於完成了一次父子組件的通信。
2一、v-model實際是一個語法糖
<input v-model="message">
<input :value="message" @input="message=$event.target.value">
這是一回事
v-model總結:
v-model是一個語法糖
在input中 這倆個是相等的
<input v-model="searchText">
等價於
<input :value="searchText" @input="searchText = $event.target.value">
固然,Vue還作了一些優化
在組件中
https://cn.vuejs.org/v2/guide/components.html#在組件上使用-v-model
<test v-model="searchText"></test>
等價於
<test :value="searchText" @input="searchText = $event"></test>
在test組件裏,要這樣寫
<template> <div class="test"> <input type="checkbox" :checked="value" @change="$emit('input', $event.target.checked)"> // 使用$emit向父組件傳回input事件 </div> </template> <script> export default { name: 'test', props: ['value'] // 這個必須有,並且默認傳過來的就是value } </script>
在實際中,咱們傳遞value和input有可能會照成歧義,這是咱們能夠自定義父組件傳過來的值和向父組件傳遞的事件
<template> <div class="test"> <input type="checkbox" :checked="checked" @change="$emit('changeChecked', $event.target.checked)"> </div> </template> <script> export default { name: 'test', model: { prop: 'checked', event: 'changeChecked' }, props: ['checked'] // 這個必須有,值就是咱們上面定義的prop } </script>
2二、slot
普通插槽是在父組件編譯和渲染階段生成 vnodes
,做爲當前組件渲染vnode的children,因此數據的做用域是父組件實例,子組件渲染的時候直接拿到這些渲染好的 vnodes
做用域插槽,父組件在編譯和渲染階段並不會直接生成 vnodes
,而是在父節點 vnode
的 data
中保留一個 scopedSlots
對象,存儲着不一樣名稱的插槽以及它們對應的渲染函數,只有在編譯和渲染子組件階段纔會執行這個渲染函數生成 vnodes
,因爲是在子組件環境執行的,因此對應的數據做用域是子組件實例。
兩種插槽的目的都是讓子組件 slot
佔位符生成的內容由父組件來決定,但數據的做用域會根據它們 vnodes
渲染時機不一樣而不一樣。
2三、keep-alive
<keep-alive>
組件是一個抽象組件,它的實現經過自定義 render
函數而且利用了插槽,而且知道了 <keep-alive>
緩存 vnode
,瞭解組件包裹的子元素——也就是插槽是如何作更新的。且在 patch
過程當中對於已緩存的組件不會執行 mounted
,因此不會有通常的組件的生命週期函數可是又提供了 activated
和 deactivated
鉤子函數。另外咱們還知道了 <keep-alive>
的 props
除了 include
和 exclude
還有文檔中沒有提到的 max
,它能控制咱們緩存的個數。
2四、
2五、vue-touter
參考網站:https://github.com/DDFE/DDFE-blog/issues/9
導航守衛的參考:http://www.javashuo.com/article/p-ahbhkegh-v.html
beforeEach
守衛。beforeRouteUpdate
守衛 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守衛 (2.5+)。afterEach
鉤子。beforeRouteEnter
守衛中傳給 next
的回調函數。
Vue-Router 內置了一個組件 <router-view>
Vue-Router 還內置了另外一個組件 <router-link>
<router-link>
比起<a href="...">
會好一些,理由以下:
不管是 HTML5 history
模式仍是 hash
模式,它的表現行爲一致,因此,當你要切換路由模式,或者在 IE9 降級使用 hash
模式,無須做任何變更。
在 HTML5 history
模式下,router-link
會守衛點擊事件,讓瀏覽器再也不從新加載頁面。
當你在 HTML5 history
模式下使用 base
選項以後,全部的 to 屬性都不須要寫(基路徑)了。
路徑變化是路由中最重要的功能,咱們要記住如下內容:路由始終會維護當前的線路,路由切換的時候會把當前線路切換到目標線路,切換過程當中會執行一系列的導航守衛鉤子函數,會更改 url,一樣也會渲染對應的組件,切換完畢後會把目標線路更新替換當前線路,這樣就會做爲下一次的路徑切換的依據。
2六、Vuex
Vuex 的初始化過程就分析完畢了,除了安裝部分,咱們重點分析了 Store
的實例化過程。咱們要把 store
想象成一個數據倉庫,初始化Vuex,就是在實例化Vuex,爲了更方便的管理倉庫,咱們把一個大的 store
拆成一些 modules
,整個 modules
是一個樹型結構。每一個 module
又分別定義了 state
,getters
,mutations
、actions
,咱們也經過遞歸遍歷模塊的方式都完成了它們的初始化。爲了 module
具備更高的封裝度和複用性,還定義了 namespace
的概念。最後咱們還定義了一個內部的 Vue
實例,用來創建 state
到 getters
的聯繫,而且能夠在嚴格模式下監測 state
的變化是否是來自外部,確保改變 state
的惟一途徑就是顯式地提交 mutation
。
Vuex 提供的一些經常使用 API 咱們就分析完了,包括數據的存取、語法糖、模塊的動態更新等。要理解 Vuex 提供這些 API 都是方便咱們在對 store
作各類操做來完成各類能力,尤爲是 mapXXX
的設計,讓咱們在使用 API 的時候更加方便,這也是咱們從此在設計一些 JavaScript 庫的時候,從 API 設計角度中應該學習的方向。