美文网首页
vue 虚拟dom的diff分析

vue 虚拟dom的diff分析

作者: 三分一把刀 | 来源:发表于2019-11-26 00:00 被阅读0次

        引用资料:https://segmentfault.com/a/1190000008782928

        最早是react有虚拟dom,效率相比直接操作dom结构提高了N倍,这两天看了下vue的虚拟dom,现在和大家一起分享,大家如果想全部看,可以看看上面的引用资料,看得仔细的话加上自己的思维去思考,应该是可以明白diff当中的奥义的。

     这里我只分享diff的算法,个人理解,欢迎交流

    几个要看懂的函数:

        1:patch

        2:patchVnode

        3:updateChildren【核心,真正对比新旧两个数组的算法】

```

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)

        }

}

```

        这里的代码其实是在匹配操作新旧两个数组,如果旧数组有,新数组没有的话,最终会移除,新数组有,旧数组没有则会被插入,只是插入的位置是新数组匹配时候旧数组index的前一位,这里几幅图解释下如何匹配的更新插入删除操作的。【别人讲的都是两个数组,四个变量,相互往中间推,任何一个先操作完就算结束】

    

新旧数组匹配示意图

    过程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较

但是我觉得这里没有讲具体怎么靠。

newStartIndex会在oldCh内部循环,从oldStartIndex到oldEndIndex,无论在oldCh内部是否找到有相同的节点,newStartIndex都会往右偏移,

    这里分两种情况,匹配上和没匹配上

    匹配上:直接插入到oldStartIndex节点前面,newStartIndex++,开始下一轮循环,同样从oldStartIndex到oldEndIndex.

    没匹配上:直接插入到oldStartIndex节点前面,循环到oldEndIndex,newStartIndex++,开始下一轮循环,同样从oldStartIndex到oldEndIndex.

      oldStartIndex++,oldEndIndex--,和newEndIndex--,会在什么情况下出现,只能出现oldStartIndex节点==newEndIndex节点或者newEndIndex节点==oldEndIndex节点或者oldEndIndex节点==newStartIndex节点,这样情况才会出现数组除了newStartIndex节点往内靠,其他节点也往内靠

    结束情况,代码当中,

        如果oldStartIdx > oldEndIdx,说明就数组先遍历完,新数组没有,说明新数组有多,然后需要将新数组新的给添加进去

     如果newStartIdx > newEndIdx,说明新数组先遍历完,旧数组没有,说明新数组变少了,旧数组有多余,得移除掉旧数组多余的。

    核心,得记住,遍历每次一直往中间靠的是newStartIndex节点,而不是所有节点

    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)

        }

相关文章

  • vue系列---vue-diff

    1.vue-diff 是什么? 提到vue的diff算法就不得不提一个名词 虚拟dom(Virtual DOM) ...

  • 第十七天

    1.你怎么理解vue中的diff算法? diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即dif...

  • vue 虚拟dom的diff分析

    引用资料:https://segmentfault.com/a/1190000008782928 最早是r...

  • 虚拟dom和diff算法

    虚拟DOM和diff算法 diff:精细化比对最小量更新 真实DOM和虚拟DOM 虚拟DOM:用JavaScrip...

  • 基于React理解虚拟DOM(Virtual DOM)和Diff

    我在之前的文章《虚拟DOM(Virtual DOM)中动态更新视图的diff算法》中,基于vue描述Virtual...

  • 理解vue2.x之diff算法

    了解diff算法前,应该先了解虚拟DOM(VNode),在vue中是先创建VNode,再通过diff算法看哪个节点...

  • 2020-04-11

    vue diff算法分析 diff存在的意义 1.对服务端和浏览器端统一一层虚拟dom,这样无论是服务器预编译ru...

  • vue2.0的diff算法详解

    目录 前言 virtual dom 分析diff 总结 前言 vue2.0加入了virtual dom,有向rea...

  • Vue3.0 的性能优化

    1.diff算法优化 vue2.0 中虚拟dom 是全量对比;vue3.0 中新增了静态标记(patchFlag)...

  • Diff 算法、key

    概念 DOM diff 就是对比两棵虚拟 DOM 树的算法。当组件变化时,会得到一个新的虚拟 DOM,diff 算...

网友评论

      本文标题:vue 虚拟dom的diff分析

      本文链接:https://www.haomeiwen.com/subject/uvujwctx.html