Skip to content

11. 快速 Diff 算法

快速 Diff 算法包含预处理步骤,这其实是借鉴了纯文本 Diff 算法的思路。

11.1  相同的前置元素和后置元素

对于相同的前置节点和后置节点,由于它们在新旧两组子节点中的相对位置不变,所以我们无须移动它们,但仍然需要在它们之间打补丁。 11-1

当相同的前置节点和后置节点被处理完毕后,旧的一组子节点已经全部被处理了,而在新的一组子节点中,遗留的未被处理的节点就是需要新增的节点。反之新的一组子节点已经全部被处理了,在旧的一组子节点中,遗留的未被处理的节点就是需要卸载的节点 11-2

js
function patchKeyedChildren(n1, n2, container) {
  const newChildren = n2.children;
  const oldChildren = n1.children;
  // 1. 处理相同的前置节点
  // 索引 j 指向新旧两组子节点的开头
  let j = 0;
  let oldVNode = oldChildren[j];
  let newVNode = newChildren[j];
  // while 循环向后遍历,直到遇到拥有不同 key 值的节点为止
  while (oldVNode.key === newVNode.key) {
    // 调用 patch 函数进行更新
    patch(oldVNode, newVNode, container);
    // 更新索引 j,让其递增
    j++;
    oldVNode = oldChildren[j];
    newVNode = newChildren[j];
  }
  // 2. 更新相同的后置节点
  // 索引 oldEnd 指向旧的一组子节点的最后一个节点
  let oldEnd = oldChildren.length - 1;
  // 索引 newEnd 指向新的一组子节点的最后一个节点
  let newEnd = newChildren.length - 1;

  oldVNode = oldChildren[oldEnd];
  newVNode = newChildren[newEnd];

  // while 循环从后向前遍历,直到遇到拥有不同 key 值的节点为止
  while (oldVNode.key === newVNode.key) {
    // 调用 patch 函数进行更新
    patch(oldVNode, newVNode, container);
    // 递减 oldEnd 和 nextEnd
    oldEnd--;
    newEnd--;
    oldVNode = oldChildren[oldEnd];
    newVNode = newChildren[newEnd];
  }

  // 3. 处理中间节点
  if (j > oldEnd && j <= newEnd) {
    // 锚点的索引
    const anchorIndex = newEnd + 1;
    // 锚点元素
    const anchor =
      anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null;
    // 采用 while 循环,调用 patch 函数逐个挂载新增节点
    while (j <= newEnd) {
      patch(null, newChildren[j++], container, anchor);
    }
  } else if (j > newEnd && j <= oldEnd) {
    // j -> oldEnd 之间的节点应该被卸载
    while (j <= oldEnd) {
      unmount(oldChildren[j++]);
    }
  }
}

11.2  判断是否需要进行 DOM 移动操作

js
if (j > oldEnd && j <= newEnd) {
  // 省略部分代码
} else if (j > newEnd && j <= oldEnd) {
  // 省略部分代码
} else {
  // 构造 source 数组
  const count = newEnd - j + 1;
  const source = new Array(count);
  source.fill(-1);

  const oldStart = j;
  const newStart = j;
  let moved = false;
  let pos = 0;
  const keyIndex = {};
  for (let i = newStart; i <= newEnd; i++) {
    keyIndex[newChildren[i].key] = i;
  }
  // 新增 patched 变量,代表更新过的节点数量
  let patched = 0;
  for (let i = oldStart; i <= oldEnd; i++) {
    oldVNode = oldChildren[i];
    // 如果更新过的节点数量小于等于需要更新的节点数量,则执行更新
    if (patched <= count) {
      const k = keyIndex[oldVNode.key];
      if (typeof k !== "undefined") {
        newVNode = newChildren[k];
        patch(oldVNode, newVNode, container);
        // 每更新一个节点,都将 patched 变量 +1
        patched++;
        source[k - newStart] = i;
        // 是否需要移动
        if (k < pos) {
          moved = true;
        } else {
          pos = k;
        }
      } else {
        // 没找到
        unmount(oldVNode);
      }
    } else {
      // 如果更新过的节点数量大于需要更新的节点数量,则卸载多余的节点
      unmount(oldVNode);
    }
  }
}
js

。。。

上次更新于: