Vue diff 算法

1,104 阅读3分钟

一、diff 算法

diff 算法是一种通过同层的树节点进行比较的高效算法,避免对树的逐层遍历,减少时间复杂度。

diff 算法的两个特点:

  • 只会同级比较,不会跨层。
  • diff 比较循环都是从两边往中间收拢。

二、Vue diff 算法

Vue的虚拟 dom diff 核心在于 patch 过程

2.1 首先将新旧 vNode 进行开始位置和结束位置的标记

let oldStartIndex = 0;
let oldEndIndex = oldChildren.length - 1;
let oldStartVnode = oldChildren[0];
let oldEndVnode = oldChildren[oldEndIndex];
let newStartIndex = 0;
let newEndIndex = newChildren.length - 1;
let newStartVnode = newChildren[0];
let newEndVnode = newChildren[newEndIndex];

2.2 标记好节点位置,进行循环处理节点

  • 如果当前 oldStartVnode 和 newStartVnode 节点相同,直接用新节点复用老节点,进行 patchVnode 复用,更新 oldStartVnode、newStartVnode、oldStartIndex++、newStartIndex++。
  • 如果当前 oldEndVnode 和 newEndVnode 节点相同,直接用新节点复用老节点,进行 patchVnode 复用,更新 oldEndVnode、newEndVnode、oldEndIndex--、newEndIndex--。
  • 如果当前 oldStartVnode 和 newEndVnode 节点相同,直接用新节点复用老节点,进行 patchVnode 复用,将老节点移动到 oldEndVnode 节点之后,更新 oldStartVnode、newEndVnode、oldStartIndex++、newEndIndex--。
  • 如果当前 oldEndVnode 和 newStartVnode 节点相同,直接用新节点复用老节点,进行 patchVnode 复用,将老节点移动到 oldStartVnode之前,更新 oldStartVnode、newEndVnode、oldEndIndex--、newStartIndex--。
  • 如果都不满足则没有相同节点复用,进行 key 的对比,满足条件进行 patchVnode 过程,并将 dom 移动到 oldStartVnode 对应的真实 dom 之前,没找到则重新创建。

2.3 递归处理

三、Vue diff 图解

47B84A48E3653C4AFC68E11EF580C5A2.png

749F2BFF145ACDEA4A51CE2CC1AE9C0D.gif

  • 第一步:创建四个指针,分别为旧 Vnode 的开始指针和结束指针,新 Vnode 的开始指针和结束指针。
  • 第二步:先比较旧 Vnode 的开始指针和新 Vnode 的开始指针,即AE,发现不是同一个节点。
  • 第三步:再比较旧 Vnode 的结束指针和新 Vnode 的结束指针,即DF,依然不是相同节点。
  • 第四步:再比较旧 Vnode 的开始指针和新 Vnode 的结束指针,即AF,不是相同节点。
  • 第五步:再比较旧 Vnode 的结束指针和新 Vnode 的开始指针,即ED,不是相同节点。
  • 第六步:通过上述四种对比方式都不是相同节点,下面就在旧 Vnode 节点中查找是否有与E节点相同的节点。
  • 第七步:发现旧 Vnode 节点中没有E节点,那么就会在旧 Vnode 开始指针前插入一个新的E节点。
  • 第八步:第一个节点操作完后,指针后移,继续进行比较,重复第二至第七步,结果为新增、删除、移动。
  • 第九步:当找到相同节点时,会通过patchVnode进行这两个节点更细致的diff

总结

每次diff都会调用updateChildren方法来比较,然后层层递归下去,直到将旧Vnode和新Vnode中的所有子节点对比完。domDiff的过程更像是两个树的比较,每找到相同节点时,都会一层层的往下比较它们的子节点,是一个深度递归遍历比较的过程。