图解Vue3的diff算法

关注公众号"知之洲"可以阅读本文全篇和其他优质干货。

原因

如果不进行对比,每次更新的时候都先卸载旧节点,然后挂载新节点,每次卸载或挂载都会造成页面重排,浪费性能,因此需要进行一些细粒度的操作,最好找到所有不同点,根据这些不同点进行细粒度的按需同步。可以从以下三个地方入手:

  1. 找到对相同的节点。(复用)
  2. 找出需要新增的虚拟DOM。(创建并插入)
  3. 元素发生位置变化,找出哪些元素需要移动。(移动)
调用时机

修改响应式属性会重新执行render函数,返回新虚拟DOM。这时候会调用patch对比新旧虚拟DOM,并返回一个patch对象,用来存储两个节点不同的地方,最后根据patch对象去更新DOM。

调用patch对比新旧虚拟DOM的时候,如果双方子级都为数组的情况下(具有多个子级节点)就会调用patchChildren方法,patchChildren方法里面会根据有没有key分成个大的方向去比较双方子级元素的差异,这里使用到的算法就是diff算法,只会进行同层比较不会进行跨层比较。

function patchChildren(...) {
  if (/*存在key*/) {
    patchKeyedChildren(oldChildren, newChildren, el);
  } else {
    patchUnkeyedChildren(oldChildren, newChildren, el);
  }
}
无key的diff算法

此种情况很简单,直接轮询。从头开始轮询,根据情况执行patch、unmount、mount操作。

function patchUnkeyedChildren(oldChildren, newChildren, el) {
  const oldLen = oldChildren.length;
  const newLen = newChildren.length;
  const commonLen = Math.min(oldLen, newLen);
  for (let i = 0; i < commonLen; i++) {
    patch(/*对比新旧节点*/);
  }
  if (oldLen > newLen) {
    unmount(/*卸载后面的节点*/);
  } else {
    mount(/*挂载后面的节点*/);
  }
}

公共对比中a、b可以复用,对比到旧节点为c后,新虚拟DOM变为f了,patch后f会替换掉c,之后的公共对比会一直替换下去。

替换过程:判断节点类型,如果相同就会复用旧的真实节点,如果不同就会删除旧的真实节点,创建新的真实节点再进行挂载。类型相同虽然会复用旧的真实节点,但仍然需要比较两个虚拟DOM,因为它们的属性、内容可能不同,然后还要对比它们的子级,如此递归。

公共对比完成之后,后面的要么是直接删除旧的真实节点,完成卸载。要么是是直接插入新的真实节点完成挂载。

有key的diff算法

从前往后递增对比,节点类型和key相同的进行复用(在不同处停止对比)。

while (i <= e1 && i <= e2) {
  const oldNode = oldChildren[i];
  const newNode = newChildren[i];
  if (isSameVNodeType(oldNode, newNode)) {
    patch(/*对比新旧节点*/);
  } else {
    break
  }
  i++
}

从后往前递减对比,节点类型和key相同的进行复用(在不同处停止对比)。

while (i <= e1 && i <= e2) {
  const oldNode = oldChildren[i];
  const newNode = newChildren[i];
  if (isSameVNodeType(oldNode, newNode)) {
    patch(/*对比新旧节点*/);
  } else {
    break
  }
  e1--;
  e2--;
}

此时,如果 i > e1 说明旧节点列表经过首尾两次对比已经被全部覆盖到,这时如果 i < e2 说明有新增节点。

如果 i > e2 说明新节点列表经过首尾两次对比已经被全部覆盖到,这时如果 i < e1 说明有需要删除的节点。

如果 i < e1 并且 i < e2,说明经过首尾两次对比新旧节点都未被全部覆盖到,此时说明中间存在乱序节点。

对于乱序的情况,新增s1和s2指针,都指向在i停止的位置。

  1. 把新子级的所有乱序元素(e、c)保存在一个映射表里(keyToNewIndexMap)。
  2. 遍历旧乱序子级,查找映射表中是否存在该元素(判断key和类型是否一致),存在就进行复用,不存在就说明需要创建新元素并插入,还要把旧节点进行删除。

关注公众号“知之洲”还可以阅读更多优质干货。

  • 43
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值