vue3.0 diff算法

391 阅读2分钟

patchChildren根据是否存在key进行真正的diff或者直接patch。

diff作用就是在patch子vnode过程中,找到与新vnode对应的老vnode,复用真实的dom节点,避免不必要的性能开销

不存在key时,patchChildren渲染dom树时调用patchUnkeyedChildren方法

  • 比较新老children的length获取最小值 然后对于公共部分,进行从新patch工作。
  • 如果老节点数量大于新的节点数量 ,移除多出来的节点。
  • 如果新的节点数量大于老节点的数量,从新 mountChildren新增的节点。
const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
    c1 = c1 || EMPTY_ARR
    c2 = c2 || EMPTY_ARR
    const oldLength = c1.length
    const newLength = c2.length
    const commonLength = Math.min(oldLength, newLength)
    let i
    for (i = 0; i < commonLength; i++) { /* 依次遍历新老vnode进行patch */
      const nextChild = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
      patch(
        c1[i],
        nextChild,
        container,
        null,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized
      )
    }
    if (oldLength > newLength) { /* 老vnode 数量大于新的vnode,删除多余的节点 */
      unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
    } else { /* /* 老vnode 数量小于于新的vnode,创造新的即诶安 */
      mountChildren(
        c2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        optimized,
        commonLength
      )
    }
}

存在key值时,调用patchKeyedChildren方法,diff算法核心如下:

变量定义如下:

    /*  c1 老的vnode c2 新的vnode  */
    let i = 0              /* 记录索引 */
    const l2 = c2.length   /* 新vnode的数量 */
    let e1 = c1.length - 1 /* 老vnode 最后一个节点的索引 */
    let e2 = l2 - 1        /* 新节点最后一个节点的索引 */

**
**

1、如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环

// 1. sync from start
// (a b) c
// (a b) d e
// 从头对比找到有相同的节点 patch ,发现不同,立即跳出
while (i <= e1 && i <= e2) {
  const n1 = c1[i];
  const n2 = (c2[i] = optimized
              ? cloneIfMounted(c2[i])
              : normalizeVNode(c2[i]));
  if (isSameVNodeType(n1, n2)) {
    patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
  }
  else {
    break;
  }
  i++;
}

2、从尾向前开始diff

// 2. sync from end
// a (b c)
// d e (b c)
// 如果第一步没有patch完,立即,从后往前开始patch ,如果发现不同立即跳出循环 
while (i <= e1 && i <= e2) {
  const n1 = c1[e1];
  const n2 = (c2[e2] = optimized
              ? cloneIfMounted(c2[e2])
              : normalizeVNode(c2[e2]));
  if (isSameVNodeType(n1, n2)) {
    patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
  }
  else {
    break;
  }
  e1--;
  e2--;
}

3、如果老节点全部被patch,新节点未patch完,创建新的vnode

// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
  if (i <= e2) {
    const nextPos = e2 + 1;
    const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
    while (i <= e2) {
      patch(null, (c2[i] = optimized
                   ? cloneIfMounted(c2[i])
                   : normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
      i++;
    }
  }
}

4、如果新节点全部被patch,老节点有剩余,则卸载所有老节点

// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
  while (i <= e1) {
    unmount(c1[i], parentComponent, parentSuspense, true);
    i++;
  }
}

5、不确定的元素 ( 这种情况说明没有patch完相同的vnode ),我们可以接着①②的逻辑继续往下看

第一步: 通过老节点的key找到对应新节点的index:开始遍历老的节点,判断有没有key, 如果存在key通过新节点的keyToNewIndexMap找到与新节点index,如果不存在key那么会遍历剩下来的新节点试图找到对应index。

第二步:如果存在index证明有对应的老节点,那么直接复用老节点进行patch,没有找到与老节点对应的新节点,删除当前老节点。

第三步:newIndexToOldIndexMap找到对应新老节点关系。

6、移动dom元素、新增新节点

我们之前有个newIndexToOldIndexMap 记录偏移量

如果newIndexToOldIndexMap 的项 为 0,为新增节点

获取newIndexToOldIndexMap 最长稳定序列

通过该序列基础作为参照,对节点进行移动