Vue3源码学习之路-研究diff算法

当数据有key时

从前向后比

声明一个i,i从0开始循环,e1为旧值长度减1,e2为新值长度减1,当i大于e1或者大于e2或者有一项不相等时就结束循环。
那么如果长度不等,那么剩余的长度就是待操作的节点。

let i = 0;
let e1 = c1.length - 1;
let e2 = c2.length - 1;
    
// 从前向后比
while (i <= e1 && i <= e2) {
  const n1 = c1[i];
  const n2 = c2[i];
  if (isSameVNode(n1, n2)) {
    patch(n1, n2, el);
  } else {
    break;
  }
  i++;
}

从后向前比

e1为旧值长度减1,e2为新值长度减1,当i大于e1或者大于e2或者有一项不相等时就结束循环。
那么如果长度不等,那么剩余的长度就是待操作的节点。

// 从后向前比
let i = 0;
let e1 = c1.length - 1;
let e2 = c2.length - 1;
while(i <= e1 && i <= e2) {
  const n1 = c1[e1];
  const n2 = c2[e2];
  if (isSameVNode(n1, n2)) {
    patch(n1, n2, el);
  } else {
    break;
  }
  e1--;
  e2--;
}

新值长度大于旧值长度(挂载)

不管是从前向后还是从后向前,如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容

// 如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容
if (i> e1) {
  if (i <= e2) {
    while (i <= e2) {
      const nextNode = e2 + 1;
      // 根据是否存在下一个node,判断是向前插入还是向后插入元素
      const anchor = nextNode < c2.length ? c2[nextNode].el : null;
      patch(null, c2[i], el, anchor); // 创建新节点到容器中
      i++;
    }
  }
}

新值长度小于旧值长度(卸载)

当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了,i到e1之间的就是卸载的

if (i > e2) {
  // 当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了
  if (i <= e1) {
    while (i <= e1) {
      unmount(c1[i]);
      i++;
    }
  }
}

乱序对比

掐头去尾,找出需要操作的数据,并将新节点索引保存,后面可以复用

// 乱序对比
let s1 = i;
let s2 = i;

// 新节点索引映射
const keyToNewIndexMap = new Map();
for (let i = s2; i <= e2; i++) {
  keyToNewIndexMap.set(c2[i].key, i);
}

// 新的个数
const toBePatched = e2 - s2 + 1;
// 记录新增的是否进行过处理
const newIndexCorOldMap = new Array(toBePatched).fill(0);

// 循环旧节点,查找是否在映射中存在
// 如果有,对比差异
// 如果没有执行卸载
for (let i = s1; i <= e1; i++) {
  const oldChild = c1[i];
  const newChildIndex = keyToNewIndexMap.get(oldChild.key);
  if (!newChildIndex) {
    unmount(oldChild);
  } else {
    newIndexCorOldMap[newChildIndex - s2] = i + 1;
    patch(oldChild, c2[newChildIndex], el);
  }
}

// 按顺序插入
for (let i = toBePatched - 1; i >= 0; i--) {
  let index = i + s2;
  let current = c2[index];
  let anchor = index + 1 < c2.length ? c2[index + 1].el : null;
  if (newIndexCorOldMap[i] === 0) {
    // 新建元素
    patch(null, current, el, anchor);
  } else {
    // 复用
    hostInsert(current.el, el, anchor);
  }
}

最长递增子序列

找出无需操作的元素,减少dom操作次数,需要找出最长递增子序列个数

比如:[14,2,8,13,6,4,12,15],求出来就是:[2,4,12,15]

贪心算法 > 二分查找 > 索引追溯

function getSequence(arr) {
  const len = arr.length;
  const res = [0];
  let indexOfLast;
  let start;
  let end;
  let middle;
  // 前一项索引标记
  const p = new Array(arr.length).fill(0);

  for (let i = 0; i < arr.length; i++) {
    const current = arr[i];
    // vue中0是代表新建
    if (current !== 0) {
      indexOfLast = res[res.length - 1];

      // 比较最后一项和当前项的值,如果比最后一项大的话就将当前值放到结果中
      if (arr[indexOfLast] < arr[i]) {
        res.push(i);
        // 记录前一项
        p[i] = indexOfLast;
        continue;
      }

      // 二分查找索引
      // 在结果中找到比当前值打的,用当前值索引替换
      start = 0;
      end = res.length - 1;

      while (start < end) {
        middle = ((start + end) / 2) | 0;

        if (arr[res[middle]] < current) {
          start = middle + 1;
        } else {
          end = middle;
        }
      }
      // 找到中间值,进行替换
      if (arr[res[end]] > current) {
        res[end] = i;
        // 记录前一项
        p[i] = res[end - 1];
      }
    }
  }

  // 通过最后一项向前追溯
  let i = res.length;
  let last = res[i-1];

  while(i-- > 0) {
    res[i] = last;
    last = p[last];
  }

  return res;
}

renderer渲染时增加节点比对逻辑

// 比较两个节点的差异
const patchKeyChildren = (c1, c2, el) => {
  let i = 0;
  let e1 = c1.length - 1;
  let e2 = c2.length - 1;

  // 从前向后比
  while (i <= e1 && i <= e2) {
    const n1 = c1[i];
    const n2 = c2[i];
    if (isSameVNode(n1, n2)) {
      patch(n1, n2, el);
    } else {
      break;
    }
    i++;
  }

  // 从后向前比
  while (i <= e1 && i <= e2) {
    const n1 = c1[e1];
    const n2 = c2[e2];
    if (isSameVNode(n1, n2)) {
      patch(n1, n2, el);
    } else {
      break;
    }
    e1--;
    e2--;
  }

  // 如果i比e1大的话说明有新增项目,i和e2之间的就是新增的内容
  if (i > e1) {
    if (i <= e2) {
      while (i <= e2) {
        const nextNode = e2 + 1;
        // 根据是否存在下一个node,判断是向前插入还是向后插入元素
        const anchor = nextNode < c2.length ? c2[nextNode].el : null;
        patch(null, c2[i], el, anchor); // 创建新节点到容器中
        i++;
      }
    }
  } else if (i > e2) {
    // 当i大于e2,并且小于等于e1时,就应该执行卸载逻辑了
    if (i <= e1) {
      while (i <= e1) {
        unmount(c1[i]);
        i++;
      }
    }
  }

  // 乱序对比
  let s1 = i;
  let s2 = i;

  // 新节点索引映射
  const keyToNewIndexMap = new Map();
  for (let i = s2; i <= e2; i++) {
    keyToNewIndexMap.set(c2[i].key, i);
  }

  // 新的个数
  const toBePatched = e2 - s2 + 1;
  // 记录新增的是否进行过处理
  const newIndexCorOldMap = new Array(toBePatched).fill(0);

  // 循环旧节点,查找是否在映射中存在
  // 如果有,对比差异
  // 如果没有执行卸载
  for (let i = s1; i <= e1; i++) {
    const oldChild = c1[i];
    const newChildIndex = keyToNewIndexMap.get(oldChild.key);
    if (!newChildIndex) {
      unmount(oldChild);
    } else {
      newIndexCorOldMap[newChildIndex - s2] = i + 1;
      patch(oldChild, c2[newChildIndex], el);
    }
  }

  // 获取最长递增序列
  let increment = getSequence(newIndexCorOldMap);

  // 按顺序插入
  let j = increment.length - 1;
  for (let i = toBePatched - 1; i >= 0; i--) {
    let index = i + s2;
    let current = c2[index];
    let anchor = index + 1 < c2.length ? c2[index + 1].el : null;
    if (newIndexCorOldMap[i] === 0) {
      // 新建元素
      patch(null, current, el, anchor);
    } else {
      // 复用
      if (i !== increment[j]) {
        hostInsert(current.el, el, anchor);
      } else {
        j--;
      }
    }
  }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值