function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { let oldStartIdx = 0 let 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, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(newCh) } while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { // oldStartVnode沒有,則oldStartIdx後移一位 oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) { // oldEndVnode沒有,則oldEndIdx前移一位 oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { // 處理 頭部 的同類型節點,即oldStartVnode和newStartVnode指向同類節點的狀況 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] // 標記: oldStartIdx 和 newStartIdx 後移1位 } else if (sameVnode(oldEndVnode, newEndVnode)) { // 處理 尾部 的同類型節點,即oldEndVnode和newEndVnode指向同類節點的狀況 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] // 標記: oldEndIdx 和 newEndIdx 前移1位 } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right 處理 頭尾 同類型節點,即oldStartVnode和newEndVnode指向同類節點的狀況 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // 把oldStartVnode的el節點元素右移到oldEndVnode的el節點後面緊跟節點元素的前面(說白了就是oldEndVnode的el節點的後面) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] // 標記: oldStartIdx 後移1位, newEndIdx 前移1位 } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left 處理 尾頭 的同類型節點,即oldEndVnode和newStartVnode指向同類節點的狀況 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // 把oldEndVnode的el節點左移到oldStartVnode的el節點的前面 oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] // 標記: oldEndIdx 前移1位 newStartIdx 後移1位 } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // oldKeyToIdx一個對象,存儲剩下oldCh的鍵值對: {key: 索引} idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 該三目是爲了查找newStartVnode在oldCh的索引 if (isUndef(idxInOld)) { // New element undefined的話,說明newStartVnode未在oldCh中找到,說明它是一個新增的節點,則建立一個新的節點,且插入到oldStartVnode的elm dom的前面 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { // 找到newStartVnode在oldCh中的索引,說明是須要處理更新的節點 vnodeToMove = oldCh[idxInOld] // 在oldCh中找到須要移動的vnode if (sameVnode(vnodeToMove, newStartVnode)) { // 若是須要移動的vnode和newStartVnode是同類節點,則把vnodeToMove的el dom移動到oldStartVnode的el dom的前面,且把剛纔移動的vnode標記爲undefined patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined // 標記爲undefined目的是: canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // 若是key相同,元素不一樣,則視爲新增元素 // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] // 若是是處理新增節點的狀況: oldCh中沒有新增節點,因此標記過程當中它的指針不須要移動,只須要把newStartIdx後移1位 // 若是是處理更新的節點的狀況: 在oldCh中該節點不在指針處,因此採用設置爲undefined的方式來標記,可是newCh中的newStartIdx後移1位 } } if (oldStartIdx > oldEndIdx) { // 當oldCh中的起止點相遇了,可是新vnode中的起止點沒有相遇,這時須要對新vnode中的未處理節點進行處理,這類節點屬於更新中被加入的節點,須要將他們插入到DOM樹中 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { // 當新vnode中的起止點相遇,且newStartIdx超過newEndIdx,須要把oldCh中oldStartIdx和oldEndIdx之間(包含他們)的dom刪除(可是oldCh還有undefined標記的則不須要刪除) removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } }
配合如下demo來看這段代碼node
const res = Vue.compile(` <div> <p v-for="(item, index) in arr" :key="item" > {{ item }} </p> </div> `) const vm = new Vue({ data: { arr: Array.apply(null, { length: 10 }).map((item, index) => { return index + 1 }) }, methods: { switchArr() { this.arr = [1, 9, 11, 7, 3, 4, 5, 6, 2, 10] } }, render: res.render, staticRenderFns: res.staticRenderFns }).$mount('#app')
1.處理頭部相同的節點算法
else if (sameVnode(oldStartVnode, newStartVnode)) { // 處理 頭部 的同類型節點,即oldStartVnode和newStartVnode指向同類節點的狀況 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] // 標記: oldStartIdx 和 newStartIdx 後移1位 }
2.處理 尾部 的同類型節點app
else if (sameVnode(oldEndVnode, newEndVnode)) { // 處理 尾部 的同類型節點,即oldEndVnode和newEndVnode指向同類節點的狀況 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] // 標記: oldEndIdx 和 newEndIdx 前移1位 }
3.處理 頭尾 同類型節點dom
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right 處理 頭尾 同類型節點,即oldStartVnode和newEndVnode指向同類節點的狀況 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // 把oldStartVnode的el節點元素右移到oldEndVnode的el節點後面緊跟節點元素的前面(說白了就是oldEndVnode的el節點的後面) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] // 標記: oldStartIdx 後移1位, newEndIdx 前移1位 }
4.處理 尾頭 的同類型節點this
else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left 處理 尾頭 的同類型節點,即oldEndVnode和newStartVnode指向同類節點的狀況 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) // 把oldEndVnode的el節點左移到oldStartVnode的el節點的前面 oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] // 標記: oldEndIdx 前移1位 newStartIdx 後移1位 }
5. 處理新增節點spa
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // oldKeyToIdx一個對象,存儲剩下oldCh的鍵值對: {key: 索引} idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 該三目是爲了查找newStartVnode在oldCh的索引 if (isUndef(idxInOld)) { // New element undefined的話,說明newStartVnode未在oldCh中找到,說明它是一個新增的節點,則建立一個新的節點,且插入到oldStartVnode的elm dom的前面 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) }
6.處理須要更新的節點指針
if (isUndef(idxInOld)) { // New element undefined的話,說明newStartVnode未在oldCh中找到,說明它是一個新增的節點,則建立一個新的節點,且插入到oldStartVnode的elm dom的前面 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { // 找到newStartVnode在oldCh中的索引,說明是須要處理更新的節點 vnodeToMove = oldCh[idxInOld] // 在oldCh中找到須要移動的vnode if (sameVnode(vnodeToMove, newStartVnode)) { // 若是須要移動的vnode和newStartVnode是同類節點,則把vnodeToMove的el dom移動到oldStartVnode的el dom的前面,且把剛纔移動的vnode標記爲undefined patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined // 標記爲undefined目的是: canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // 若是key相同,元素不一樣,則視爲新增元素 // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] // 若是是處理新增節點的狀況: oldCh中沒有新增節點,因此標記過程當中它的指針不須要移動,只須要把newStartIdx後移1位 // 若是是處理更新的節點的狀況: 在oldCh中該節點不在指針處,因此採用設置爲undefined的方式來標記,可是newCh中的newStartIdx後移1位
7.繼續處理頭部相同的節點code
8.處理oldCh中未處理的節點刪除對象
if (oldStartIdx > oldEndIdx) { // 當oldCh中的起止點相遇了,可是新vnode中的起止點沒有相遇,這時須要對新vnode中的未處理節點進行處理,這類節點屬於更新中被加入的節點,須要將他們插入到DOM樹中 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { // 當新vnode中的起止點相遇,且newStartIdx超過newEndIdx,須要把oldCh中oldStartIdx和oldEndIdx之間(包含他們)的dom刪除(可是oldCh還有undefined標記的則不須要刪除) removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) }
至此diff算法結束了。blog