目前公司主要技術棧是Vue,為了更好的使用,完成的了解Vue的原理是很有必要的。剛開始直接閱讀Vue源碼時,發現自己閱讀的效率很低。偶然間(其實不偶然)
在Github中發現了這份筆記 如何學習Vue2源碼,完整的記錄了實作Vue架構的完整過程。
我fork了作者的項目,跟着作者的思路如何學習Vue2源碼,完整走了一遍如何實作一個2.X版本的Vue。閱讀過程中我拉了一個新分支如何學習Vue2源碼(帶注釋),添加了詳細的注釋,友善自己記憶。通過閱讀作者的github,節省自己大量時間~
下面内容為自己的筆記,自己整理思路用。
建議閱讀這位大神的github:如何學習Vue2源碼。
我的閱讀記錄: 如何學習Vue2源碼(帶注釋)
Virtual DOM
參考:https://mp.weixin.qq.com/s/QXQAPEXojB9Zvri6GdXcJg
- 生成virtual DOM
- 用js對象模拟DOM樹(html文法樹?)
- render真實DOM
- virtual對象的render函數
- 生成新的virtual DOM
- render新的真實DOM
- diff新舊真實DOM,儲存變更的patches
- diff算法複雜度問題
- 深度優先周遊,每周遊一個node同時跟新的樹比較,記錄差異
- 差異類型,不同類型處理方式不一緻
- 清單對比算法,調換順序最優解?
- 在舊的真實DOM上應該patches
- 不同的patch類型,不同的處理方式
HTML parser
- Token解析
- HTML字元串 --> Token數組
- 文法樹解析
- Token數組 --> HTML AST
- VNode樹
- Dom樹
首先有2個重要的概念要留意,将會貫穿後續的文章:靜态、動态(runtime)。
我們給一下以下簡單的定義:
靜态: 任何時刻運作轉換函數,同一個輸入得到的資料結構都是一緻的
動态: 存在某一時刻運作轉換函數,同一個輸入得到的資料結構是不一緻的
是以上邊所有涉及到的資料結構可以歸為:
字元串 是 靜态 的
HTMLElment Token 流 是 靜态 的
AST 樹 是 靜态 的
VNode 樹 是 動态 的
DOM 樹 是 動态 的
建構一個最簡單的資料綁定的Vue
AST樹、VNode render、VNode三者之間的漸進關系
VNode 的屬性 attrs 和 props
控制語句
v-if v-else-if v-else v-for
新增文法步驟同上及幾章:
- parse階段增加解析v-for v-if部分的代碼,在AST上添加這部分文法的屬性
- 生成render函數階段,同樣要增加将AST轉換成render函數的fn,
- 生成Vnode階段,在單用render函數的時候,需要将v-if文法轉換為實際的VNode對象
- path階段 将VNode樹轉換成DOM樹
響應式原理
基于Object.defineProperty實作data的資料監聽,set 寫操作的時候,我們要調用 vm._update() 進行視圖更新。
深入了解Vue的computed實作原理及其實作方式
深度追蹤依賴變化
obsever、dep、watch之間的關系:https://github.com/coderzzp/vue-come-true/tree/master/vue-come-true-First
深入淺出 - vue變化偵測原理
詳細源碼分析:https://segmentfault.com/a/1190000014360080
- 收集依賴流程:
observe ->
walk ->
defineReactive ->
get ->
dep.depend() ->
watcher.addDep(new Dep()) ->
watcher.newDeps.push(dep) ->
dep.addSub(new Watcher()) ->
dep.subs.push(watcher)
- 視圖更新流程:
set ->
dep.notify() ->
subs[i].update() ->
watcher.run() || queueWatcher(this) ->
watcher.get() || watcher.cb ->
watcher.getter() ->
vm._update() ->
vm.__patch__()
事件處理
時間的處理與之前屬性的處理基本一緻:
a. 以下 HTML:
b. 解析後得到的 AST 節點:
evtAstElm = {
type: 1,
tag: 'button',
events: {
'click': { value: 'clickme' }
},
children: [ /* blabla.. */ ]
}
c. 生成的 render code:
_c('button', {
on: { "click": clickme }
}, [ _v("click me")] )
d. 得到一個帶屬性的 VNode 節點:
VNode {
tag: 'button',
data: {
on: { "click": clickme }
},
children: [ /* blabla.. */ ]
}
c. 生成的 render code ( clickme 函數需要代理到目前的 vm 對象上,同時綁上 vm 這個運作時 context):
_c('button', {
on: { "click": clickme }
}, [ _v("click me")] )
e. 最後渲染在 dom 上的時候:
源碼的事例 https://github.com/raphealguo/how-to-learn-vue2-blob/blob/master/articles/2.4.1.md
生命周期
- new Vue()
-
初始化生命周期、往vue執行個體上綁事件、方法
這裡隻的事件、方法,并不是使用者自己綁定的method、data之類的資料,而是Vue執行個體本身的一些内部方法。
- 執行鈎子:
beforeCreate()
beforeCreate()
鈎子中無法通路到,data、method、$el等資料
$el挂載點,也就是vue生成的dom挂載的地方
-
初始化狀态
簡單來說就是初始化method、data,監聽資料之類的操作
- 執行鈎子:
data、method方法已經能通路了,但是還沒有挂載點資訊created()
-
是否有挂載點?
有:擷取模闆資訊(有無template,兩種情況),
無:暫停執行,知道使用編譯 -> vnode -> render函數 -> Dom -> diff -> patch
手動挂載後,繼續執行.$mount()
- 執行鈎子:
beforeMount()
中已經有挂載點了,但是dom資源還沒有渲染.$el
- 将VNode渲染成Dom,并挂載到挂載點上。
得到更新。$el
- 執行鈎子
挂載完成。mounted()
- data改變時,觸發監聽。
- 執行
此時僅監聽到了data改變,還未做出操作beforeUpdate()
-
對比VNode變化,更新domdiff VNode -> patch
- 檢測到頁面銷毀(
被執行).$destroy()
- 執行鈎子
beforeDestroy()
- 移除data、mothod、watcher、components、event listener等
- 執行鈎子
destroyed()