-
diff的時機
當組件創建時,以及依賴的屬性或數據變化時,會運行一個函數,該函數會做兩件事:
- 運行_render生成一棵新的虛擬dom樹(vnode tree),返回根節點
- 運行_update,傳入虛擬dom樹的根節點,對新舊兩棵樹進行對比,最終完成對真實dom的更新
核心代碼如下:
function Vue() {// ...let updateComponent = () => {this._update(this._render())}new Watcher(updateComponent)// ... }
diff就發生在_update函數的運行過程中。
-
_update函數在干什么
_update函數接收到一個vnode參數,這就是新生成的虛擬dom樹。
同時,update函數通過當前組件的_vnode屬性,拿到舊的虛擬dom樹。
_update函數首先會給組件的_vnode屬性重新賦值,讓它指向新樹。
function update(vnode) {// vnode 新節點// this._vnode 舊節點let oldVNode = this._vnodethis._vnode = vnode// 對比新舊節點 更新真實 dom}
然后會判斷舊樹存不存在:
- 不存在:說明這是第一次加載組件,于是通過內部的patch函數,直接遍歷新樹,為每個節點生成真實DOM,掛載到每個節點的elm屬性上
if (!oldVNode) {this.__patch__(this.$el, vnode) }
- 存在:說明之前已經渲染過該組件,于是通過內部的patch函數,對新舊兩棵樹進行對比,以達到下面兩個目標:
- 完成對所有真實dom的最小化處理
- 讓新樹的節點對應合適的真實dom
-
patch 函數的對比流程
術語解釋:
- 「相同」:是指兩個虛擬節點的標簽類型、key值均相同,但input元素還要看type屬性
- 「新建元素」:是指根據一個虛擬節點提供的信息,創建一個真實dom元素,同時掛載到虛擬節點的elm
屬性上 - 「銷毀元素」:是指:vnode.elm.remove(), 移除真實 dom 元素
- 「更新」:是指對兩個虛擬節點進行對比更新,它僅發生在兩個虛擬節點「相同」的情況下
- 「對比子節點」:是指對兩個虛擬節點的子節點進行對比,深度優先遍歷
詳細流程:
- 根節點比較
patch函數首先對根節點進行比較。
如果兩個節點:
- 「相同」,進入「更新」流程
- 將|舊節點的真實dom賦值到新節點:newVnode.elm=oldVnode.elm
- 對比新節點和舊節點的屬性,有變化的更新到真實dom中
- 當前兩個節點處理完畢,開始「對比子節點」
- 不「相同」
- 新節點遞歸「新建元素」
- 舊節點「銷毀元素」
- 「對比子節點」
- 盡量少的進行操作
- 不行的話,盡量僅改動元素屬性
- 還不行的話,盡量移動元素,而不是刪除和創建元素
- 還不行的話,刪除和創建元素
總結
當組件創建和更新時,vue均會執行內部的update函數,該函數使用render函數生成虛擬dom樹,將新舊兩樹進行對比,找到差異點,最終更新到真實dom。
對比差異的過程叫diff,Vue在內部通過一個叫patch的函數完成該過程。
在對比時,Vue采用深度優先、同層比較的方式進行比對。
在判斷兩個節點是否相同時,vue是通過虛擬節點的key和tag來進行判斷的。
具體來說,首先對根節點進行對比,如果相同則將舊節點關聯的真實 dom 的引用掛到新節點上,然后根據需
要更新屬性到真實 dom,然后再對比其子節點數組;如果不相同,則按照新節點的信息遞歸創建所有真實
dom,同時掛到對應虛擬節點上,然后移除掉舊的dom。
在對比其子節點數組時,Vue 對每個子節點數組使用了兩個指針,分別指向頭尾,然后不斷向中間靠攏來進行
對比,這樣做的目的是盡量復用真實dom,盡量少的銷毀和創建真實dom。如果發現相同,則進入和根節點一樣的對比流程,如果發現不同,則移動真實dom到合適的位置。
這樣一直遞歸的遍歷下去,直到整棵樹完成對比。