function sameVnode(oldVnode, vnode){ return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel }
patchVnode (oldVnode, vnode) { const el = vnode.el = oldVnode.el let i, oldCh = oldVnode.children, ch = vnode.children if (oldVnode === vnode) return if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) { api.setTextContent(el, vnode.text) }else { updateEle(el, vnode, oldVnode) if (oldCh && ch && oldCh !== ch) { updateChildren(el, oldCh, ch) }else if (ch){ createEle(vnode) //create el's children dom }else if (oldCh){ api.removeChildren(el) } } }
節點的比較有5種狀況vue
if (oldVnode === vnode)
,他們的引用一致,能夠認爲沒有變化。if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text)
,文本節點的比較,須要修改,則會調用Node.textContent = vnode.text。if( oldCh && ch && oldCh !== ch )
, 兩個節點都有子節點,並且它們不同,這樣咱們會調用updateChildren函數比較子節點,這是diff的核心。else if (ch)
,只有新的節點有子節點,調用createEle(vnode),vnode.el已經引用了老的dom節點,createEle函數會在老dom節點上添加子節點。else if (oldCh)
,新節點沒有子節點,老節點有子節點,直接刪除老節點。updateChildren (parentElm, oldCh, newCh) { let oldStartIdx = 0, newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx let idxInOld let elmToMove let before while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (oldStartVnode == null) { //對於vnode.key的比較,會把oldVnode = null oldStartVnode = oldCh[++oldStartIdx] }else if (oldEndVnode == null) { oldEndVnode = oldCh[--oldEndIdx] }else if (newStartVnode == null) { newStartVnode = newCh[++newStartIdx] }else if (newEndVnode == null) { newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] }else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode) api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode) api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else { // 使用key時的比較 if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表 } idxInOld = oldKeyToIdx[newStartVnode.key] if (!idxInOld) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) newStartVnode = newCh[++newStartIdx] } else { elmToMove = oldCh[idxInOld] if (elmToMove.sel !== newStartVnode.sel) { api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el) }else { patchVnode(elmToMove, newStartVnode) oldCh[idxInOld] = null api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el) } newStartVnode = newCh[++newStartIdx] } } } if (oldStartIdx > oldEndIdx) { before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx) }else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } }
主要的思路大概就是,定義變量,分別記錄當前比較的新、舊節點中的首尾索引與節點(oldStartVnode、oldEndVnode、newStartVnode、newEndVnode)(後續稱爲比較區間)node
(圖片來自:https://github.com/aooy/blog/... )git
經過對 oldStartVnode、oldEndVnode、newStartVnode、newEndVnode 作,兩兩的 sameVnode 比較github
比較判斷有4種,按順序依次比較
oldStartVnode —— newStartVnode (頭對頭)
oldEndVnode —— newEndVnode (尾對尾)
oldStartVnode —— newEndVnode (頭對尾)
oldEndVnode —— newStartVnode (尾對頭)
若是其中一種比較成立了,那麼oldVode中的相應節點會 以當前比較區間爲基準 移到newVnode相應的位置上,
而後比較區間會根據當前的比較條件類型,以頭或尾爲縮小比較區間的方向,縮小區間算法
例如: 當oldStartVnode,newEndVnode值得比較時, 將oldStartVnode.el移動到oldEndVnode.el後邊
當4種比較都不成立時,會使用key去比較,並在最終都使newVode的比較區間,頭部 減1
api
當oldVnode的key列表中能匹配到對應的key時,判斷比較節點的選擇器屬性是否同樣dom
不同則直接在當前比較區間的頭部,新建立
一個newVnode的Dom插入函數
比較節點中的oldVnode無需處理,由於後面的比較中不會有成立的比較條件,最終會直接刪除節點
若是最終key列表也沒能匹配到的話,也是直接在當前比較區間的頭部,新建立
一個newVnode的Dom插入。spa
最後在結束時會存在2種狀況code
oldStartIdx > oldEndIdx
oldVnode先遍歷完,證實newVode有新增的節點,或者一致,這種狀況會將newVode剩餘的節點插入到oldVnode比較區間的末尾
newStartIdx > newEndIdx
這時是newVode先遍歷完,證實newVode裏刪除了某些節點,此時oldVnode的比較區間節點是已經不存在的,會將他們刪除。
參考文章: 解析vue2.0的diff算法