通過對 vue2.0 原始碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準備陸續寫:先說一下為什麼會有虛擬dom比較這一階段,我們知道了vue是資料驅動檢視(資料的變化將引起檢視的變化),但你發現某個資料改變時,檢視是區域性重新整理而不是整個重新渲染,如何精準的找到資料對應的檢視並進行更新呢?那就需要拿到資料改變前後的dom結構,找到差異點並進行更新!其中包含自己的理解和原始碼的分析,盡量通俗易懂!由於是2.0的最早提交,所以和最新版本有很多差異、bug,後續將陸續補充,敬請諒解!包含中文注釋的vue原始碼已上傳...
虛擬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()
方法很簡單,就是比較tag和key是否一致。
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', ......]所有資料比較完後,就到子節點的比較了。先判斷當前vnode是否為文字節點,如果是文字節點就不用考慮子節點的比較;若是元素節點,就需要分三種情況考慮:eg:
後面兩種情況都比較簡單,我們直接對第一種情況,子節點的比較進行分析。
子節點比較這部分**比較多,先說說原理後面再貼**。先看一張子節點比較的圖:
圖中的oldch和newch
分別表示新舊子節點陣列,它們都有自己的頭尾指標oldstartidx
,oldendidx
,newstartidx
,newendidx
,陣列裡面儲存的是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也將指...