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来看这段代码

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.处理 尾部 的同类型节点

else if (sameVnode(oldEndVnode, newEndVnode)) {  // 处理 尾部 的同类型节点,即oldEndVnode和newEndVnode指向同类节点的情况
                patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
                oldEndVnode = oldCh[--oldEndIdx]
                newEndVnode = newCh[--newEndIdx] // 标记: oldEndIdx 和 newEndIdx 前移1位
            }

3.处理 头尾 同类型节点

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.处理 尾头 的同类型节点

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位 
            }

扫描二维码关注公众号,回复: 1822286 查看本文章

5. 处理新增节点

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.继续处理头部相同的节点

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算法结束了。

猜你喜欢

转载自www.cnblogs.com/sorrowx/p/9250711.html
今日推荐