简述 Vue2 的 Diff VNode 原理

说明:本文内容以 vue-2.6.14dist/vue.js 为例


做 Diff VNode 的目的是尽量复用页面已存在的 Element,达到提高性能的目的。

方法的入口是 updateChildren,该函数内会用到另外 4 个函数,分别是判断虚拟节点是否相同的 sameVnode 函数、将旧虚拟节点的页面元素赋给新虚拟节点的 patchVnode 函数、给虚拟节点创建页面元素的 addVnodes 函数和删除虚拟节点的 removeVnodes 函数。

updateChildren 方法接收 5 个参数,依次是 parentElmoldChnewChinsertedVnodeQueueremoveOnly

我们目前只需要关注 oldChnewCh,分别是旧虚拟节点队列和新虚拟节点队列,Diff VNode 比的就是这两个队列。下面具体说说对比的方法。

首先给两个队列的头尾各放一个指针,这样我们就会有 4 个指针,分别是旧队列的左指针 oldStartIdx 和右指针 oldEndIdx,它们指向的 VNode 分别是 oldStartVnode,oldEndVnode;新队列的左指针 newStartIdx 和右指针 newEndIdx,它们指向的 VNode 分别是 newStartVnode,newEndVnode,然后开始执行移动指针进行比对。

顺便说一下,以下说的“相同”是通过 sameVnode 函数判断的。

下面是指针移动规则:

  1. 如果 oldStartIdx 指向的 VNode 未定义,则 oldStartIdx 右移一位,即 oldStartIdx += 1
  2. 如果 oldEndIdx 指向的 VNode 未定义,则 oldStartIdx 左移一位,即 oldStartIdx -= 1
  3. 如果 oldStartVnodenewStartVnode 相同,则两个左指针均右移一位
  4. 如果 oldEndtVnodenewEndVnode 相同,则两个右指针均左移一位
  5. 如果 oldStartVnodenewEndVnode 相同,则 oldStartIdx 右移,newEndIdx 左移
  6. 如果 oldEndVnodenewStartVnode 相同,则 oldEndIdx 左移,newStartIdx 右移

以上 2 到 4 步会将两个相同虚拟节点传入 patchVnode 函数,将旧虚拟节点关联的页面元素赋给新虚拟节,即 vnode.elm = oldVnode.elm

以上步骤都没成功就会判断新虚拟节点是否在旧虚拟节点中出现过:

  1. 如果没出现过,则创建新的页面元素,并赋给 newStartVnode
  2. 如果出现过且相同,则 patchVnode 两个虚拟节点
  3. 否则同第一步

重复执行以上 6 + 3 共 9 个步骤,直到两个队列至少有一组指针相错,即 startIndex > endIndex,如果是 oldStartIndex > oldEndIndex,则说明新队列还有 ≥ 0 个 VNode 没处理,这时候调用 addVnodes 循环给多出来的 VNode 通过 createElm 创建元素;同样的,如果 newStartIndex > newEndIndex,则说明新队列比较短,通过 removeVnodes 循环删除旧队列里多余的元素。

以上是 Diff VNode 的大致思路,希望我说清楚了,但最好的办法还是自己动手画两个队列,然后模拟几种情况(新增、删除、插入等)。

PS:顺便吐槽下,我觉得 sameVnodeaddVnodes 的方法名取的不是很好,前者用的 same 不是一个动词,对于方法或函数来说,最好是动词开头;后者给人的感觉是新增虚拟节点,其实只是新增页面元素关联给虚拟节点。

posted @ 2021-08-26 22:01  尹宇星_Kim  阅读(275)  评论(0编辑  收藏  举报