Vue原始碼解析 虛擬dom比較原理

2021-09-12 17:58:36 字數 4441 閱讀 3485

通過對 vue2.0 原始碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準備陸續寫:

其中包含自己的理解和原始碼的分析,盡量通俗易懂!由於是2.0的最早提交,所以和最新版本有很多差異、bug,後續將陸續補充,敬請諒解!包含中文注釋的vue原始碼已上傳...

先說一下為什麼會有虛擬dom比較這一階段,我們知道了vue是資料驅動檢視(資料的變化將引起檢視的變化),但你發現某個資料改變時,檢視是區域性重新整理而不是整個重新渲染,如何精準的找到資料對應的檢視並進行更新呢?那就需要拿到資料改變前後的dom結構,找到差異點並進行更新!

虛擬dom實質上是針對真實dom提煉出的簡單物件。就像乙個簡單的div包含200多個屬性,但真正需要的可能只有tagname,所以對真實dom直接操作將大大影響效能!

簡化後的虛擬節點(vnode)大致包含以下屬性:

,         // 屬性資料,包括class、style、event、props、attrs等

children: , // 子節點陣列,也是vnode結構

text: undefined, // 文字

elm: undefined, // 真實dom

key: undefined // 節點標識

}

虛擬dom的比較,就是找出新節點(vnode)和舊節點(oldvnode)之間的差異,然後對差異進行打補丁(patch)。大致流程如下

整個過程還是比較簡單的,新舊節點如果不相似,直接根據新節點建立dom;如果相似,先是對data比較,包括class、style、event、props、attrs等,有不同就呼叫對應的update函式,然後是對子節點的比較,子節點的比較用到了diff演算法,這應該是這篇文章的重點和難點吧。

值得注意的是,在children compare過程中,如果找到了相似的childvnode,那它們將遞迴進入新的打補丁過程。

這次的原始碼解析寫簡潔一點,寫太多發現自己都不願意看 (┬_┬)

先來看patch()函式:

function patch (oldvnode, vnode)  else 

} return vnode.elm;

}

patch()函式接收新舊vnode兩個引數,傳入的這兩個引數有個很大的區別:oldvnode的elm指向真實dom,而vnode的elm為undefined...但經過patch()方法後,vnode的elm也將指向這個(更新過的)真實dom。

判斷新舊vnode是否相似的samevnode()方法很簡單,就是比較tagkey是否一致。

function samevnode (a, b)
對於新舊vnode不一致的處理方法很簡單,就是根據vnode建立真實dom,代替oldvnode中的elm插入dom文件。

對於新舊vnode一致的處理,就是我們前面經常說到的打補丁了。具體什麼是打補丁?看看patchvnode()方法就知道了:

function patchvnode (oldvnode, vnode) 

// 判斷是否為文字節點

if (vnode.text == undefined) else if (isdef(ch)) else if (isdef(oldch)) else if (isdef(oldvnode.text))

} else if (oldvnode.text !== vnode.text)

}

打補丁其實就是呼叫各種update***()函式,更新真實dom的各個屬性。每個的update函式都類似,就拿updateattrs()舉例看看:

function updateattrs (oldvnode, vnode) 

const attrs = vnode.data.attrs || {}

// 更新/新增屬性

for (key in attrs) else

}} // 刪除新節點不存在的屬性

for (key in oldattrs)

}}

屬性(attribute)的更新函式的大致思路就是:

你會發現裡面有個booleanattrsdict[key]的判斷,是用於判斷在不在布林型別屬性字典中。

['allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', ......]

eg:

所有資料比較完後,就到子節點的比較了。先判斷當前vnode是否為文字節點,如果是文字節點就不用考慮子節點的比較;若是元素節點,就需要分三種情況考慮:

後面兩種情況都比較簡單,我們直接對第一種情況,子節點的比較進行分析。

子節點比較這部分**比較多,先說說原理後面再貼**。先看一張子節點比較的圖:

圖中的oldch和newch分別表示新舊子節點陣列,它們都有自己的頭尾指標oldstartidxoldendidxnewstartidxnewendidx,陣列裡面儲存的是vnode,為了容易理解就用a,b,c,d等代替,它們表示不同型別標籤(div,span,p)的vnode物件。

子節點的比較實質上就是迴圈進行頭尾節點比較。迴圈結束的標誌就是:舊子節點陣列或新子節點陣列遍歷完,(即oldstartidx > oldendidx || newstartidx > newendidx)。大概看一下迴圈流程

先看看沒有key的情況,放個**看得更清楚些!

但結束迴圈後,有兩種情況需要考慮:

上面說了這麼多都是沒有key的情況,說新增了:key可以優化v-for的效能,到底是怎麼回事呢?因為v-for大部分情況下生成的都是相同tag的標籤,如果沒有key標識,那麼相當於每次頭頭比較都能成功。你想想如果你往v-for繫結的陣列頭部push資料,那麼整個dom將全部重新整理一遍(如果陣列每項內容都不一樣),那加了key會有什麼幫助呢?這邊引用一張圖:

key的情況,其實就是多了一步匹配查詢的過程。也就是上面迴圈流程中的第五步,會嘗試去舊子節點陣列中找到與當前新子節點相似的節點,減少dom的操作!

有興趣的可以看看**:

function updatechildren (parentelm, oldch, newch)  else if (isundef(oldendvnode))  else if (samevnode(oldstartvnode, newstartvnode))  else if (samevnode(oldendvnode, newendvnode))  else if (samevnode(oldstartvnode, newendvnode))  else if (samevnode(oldendvnode, newstartvnode))  else  else 

}} // 迴圈結束時,刪除/新增多餘dom

if (oldstartidx > oldendidx) else if (newstartidx > newendidx)

}

希望看完這篇對虛擬dom的比較會有一定的了解!如果有什麼錯誤記得悄悄告訴我啊哈哈。

文筆還是不好,希望大家能理解o(︶︿︶)o

Vue原始碼解析 虛擬dom比較原理

通過對 vue2.0 原始碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準備陸續寫 其中包含自己的理解和原始碼的分析,盡量通俗易懂!由於是2.0的最早提交,所以和最新版本有很多差異 bug,後續將陸續補充,敬請諒解!包含中文注釋的vue原始碼已上傳.先說一下為什麼...

Vue原始碼解析 虛擬dom比較原理

通過對 vue2.0 原始碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準備陸續寫 其中包含自己的理解和原始碼的分析,盡量通俗易懂!由於是2.0的最早提交,所以和最新版本有很多差異 bug,後續將陸續補充,敬請諒解!包含中文注釋的vue原始碼已上傳.先說一下為什麼...

Vue虛擬dom原始碼解析

vue原始碼解析 虛擬dom比較原理 function patch oldvnode,vnode else return vnode.elmpatch 函式接收新舊vnode,oldvnode的elm指向真實dom,vnode的elm為undefined,經過patch方法,vnode的elm也將指...