简述 Vue2 的 Diff VNode 原理
说明:本文内容以 vue-2.6.14 的 dist/vue.js 为例
做 Diff VNode 的目的是尽量复用页面已存在的 Element,达到提高性能的目的。
方法的入口是 updateChildren,该函数内会用到另外 4 个函数,分别是判断虚拟节点是否相同的 sameVnode 函数、将旧虚拟节点的页面元素赋给新虚拟节点的 patchVnode 函数、给虚拟节点创建页面元素的 addVnodes 函数和删除虚拟节点的 removeVnodes 函数。
updateChildren 方法接收 5 个参数,依次是 parentElm、oldCh、newCh、insertedVnodeQueue 和 removeOnly。
我们目前只需要关注 oldCh 和 newCh,分别是旧虚拟节点队列和新虚拟节点队列,Diff VNode 比的就是这两个队列。下面具体说说对比的方法。
首先给两个队列的头尾各放一个指针,这样我们就会有 4 个指针,分别是旧队列的左指针 oldStartIdx 和右指针 oldEndIdx,它们指向的 VNode 分别是 oldStartVnode,oldEndVnode;新队列的左指针 newStartIdx 和右指针 newEndIdx,它们指向的 VNode 分别是 newStartVnode,newEndVnode,然后开始执行移动指针进行比对。
顺便说一下,以下说的“相同”是通过 sameVnode 函数判断的。
下面是指针移动规则:
- 如果
oldStartIdx指向的 VNode 未定义,则oldStartIdx右移一位,即oldStartIdx += 1 - 如果
oldEndIdx指向的 VNode 未定义,则oldStartIdx左移一位,即oldStartIdx -= 1 - 如果
oldStartVnode和newStartVnode相同,则两个左指针均右移一位 - 如果
oldEndtVnode和newEndVnode相同,则两个右指针均左移一位 - 如果
oldStartVnode和newEndVnode相同,则oldStartIdx右移,newEndIdx左移 - 如果
oldEndVnode和newStartVnode相同,则oldEndIdx左移,newStartIdx右移
以上 2 到 4 步会将两个相同虚拟节点传入 patchVnode 函数,将旧虚拟节点关联的页面元素赋给新虚拟节,即 vnode.elm = oldVnode.elm。
以上步骤都没成功就会判断新虚拟节点是否在旧虚拟节点中出现过:
- 如果没出现过,则创建新的页面元素,并赋给
newStartVnode - 如果出现过且相同,则
patchVnode两个虚拟节点 - 否则同第一步
重复执行以上 6 + 3 共 9 个步骤,直到两个队列至少有一组指针相错,即 startIndex > endIndex,如果是 oldStartIndex > oldEndIndex,则说明新队列还有 ≥ 0 个 VNode 没处理,这时候调用 addVnodes 循环给多出来的 VNode 通过 createElm 创建元素;同样的,如果 newStartIndex > newEndIndex,则说明新队列比较短,通过 removeVnodes 循环删除旧队列里多余的元素。
以上是 Diff VNode 的大致思路,希望我说清楚了,但最好的办法还是自己动手画两个队列,然后模拟几种情况(新增、删除、插入等)。
PS:顺便吐槽下,我觉得 sameVnode 和 addVnodes 的方法名取的不是很好,前者用的 same 不是一个动词,对于方法或函数来说,最好是动词开头;后者给人的感觉是新增虚拟节点,其实只是新增页面元素关联给虚拟节点。

浙公网安备 33010602011771号