zoukankan      html  css  js  c++  java
  • 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位 
                }

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

  • 相关阅读:
    win8系统下,python 2.7安装xlrd,xlutils和xlwt的方法
    phone number
    string to int
    Linux P2V Azure
    Azure IaaS 知识点小节
    Exchange端口列表
    自签名证书和私有CA签名的证书的区别 创建自签名证书 创建私有CA 证书类型 证书扩展名(转)
    Power BI REST API
    Web App 压力测试
    Azure IaaS限制
  • 原文地址:https://www.cnblogs.com/sorrowx/p/9250711.html
Copyright © 2011-2022 走看看