vue diff 算法學習

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

相關文章
相關標籤/搜索