《Vue.js设计与实现》--- 笔记

第十章 双端Diff算法(vue2)

10.1 双端比较的原理

图10-5 双端比较的方式

  • 第一步:比较旧的一组子节点中第一个子节点p-1与新的一组子节点中的第一个子节点p-4,由于两者的key值不同,因此不相同,不可复用,于是什么都不做。
  • 第二步:比较旧的一组子节点中的最后一个子节点p-4与新的一组子节点中最后一个子节点p-3,由于两者key值不同,因此不同,不可复用,于是什么都不做。
  • 第三步:比较旧的一组子节点中第一个子节点p-1与新的一组子节点中的最后一个子节点p-3,由于两者key值不同,因此不同,不可复用,于是什么都不做
  • 第四步:比较旧的一组子节点中最后一个子节点p-4与新的一组子节点中的第一个子节点p-4,由于它们key值相同,因此可以进行DOM复用

图10-6 新旧两组子节点以及真实DOM节点的状态

  • DOM移动:将索引oldEndIdx指向的虚拟节点对应的真是DOM移动到oldStartIdx指向的虚拟节点对应的真实DOM前面
  • 移动DOM完成后,更新索引值,并指向下一个位置

图10-7 新旧两组子节点以及真实DOM节点的状态

  • 重复第一步,p-1p-2不同不做动作
  • 重复第二步,p-3相同,可复用。另外,由于两者皆处于尾部,因此不需要对真实DOM进行移动,只需要打补丁即可
  • 更新索引值,并指向下一个位置

图10-8 新旧两组子节点以及真实DOM节点的状态

  • 重复第一步,p-1p-2不同不做动作
  • 重复第二步,p-2p-1不同不做动作
  • 重复第三步,p-1相同,可以复用
  • 在第三步中找到相同节点,这说明p-1原本是头部节点,但在新顺序中,它变成了尾部节点。因此,需要将p-1对应的真实DOM移动到旧的一组节点的尾部节点p-2对应的真实DOM后面
    • 如果旧的一组子节点的头部节点与新的一组子节点的尾部节点匹配,说明旧节点所对应的真实DOM节点需要移动到尾部
    • 因此,需要获取当前尾部节点的下一个兄弟节点作为锚点,即oldEndVNode.el.nextSibling
  • 更新索引值,并指向下一个位置

图10-9 新旧两组子节点以及真实DOM节点的状态

  • 重复第一步,p-2相同,可复用。但两者在新旧两组子节点中都是头部节点,因此不需要移动,只需要调用patch函数打补丁即可

图10-10 新旧两组子节点以及真实DOM节点的状态

10.3 非理想状况的处理

图10-17 第一轮比较都无法命中

  • 双端diff四个步骤都无法命中的情况下,拿新的一组子节点中的头部节点去旧的一组节点中寻找

图10-18 在旧子节点中寻找可复用节点

  • 节点p-2原本不是头部节点,但在更新之后成为了头部节点。所以需要将p-2对应的真实DOM移动到当前旧的一组子节点的头部节点p-1对应的真实DOM节点之前
01 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
02   if (oldStartVNode.key === newStartVNode.key) {
03     // 省略部分代码
04   } else if (oldEndVNode.key === newEndVNode.key) {
05     // 省略部分代码
06   } else if (oldStartVNode.key === newEndVNode.key) {
07     // 省略部分代码
08   } else if (oldEndVNode.key === newStartVNode.key) {
09     // 省略部分代码
10   } else {
11     // 遍历旧 children,试图寻找与 newStartVNode 拥有相同 key 值的元素
12     const idxInOld = oldChildren.findIndex(
13       node => node.key === newStartVNode.key
14     )
15     // idxInOld 大于 0,说明找到了可复用的节点,并且需要将其对应的真实 DOM 移动到头部
16     if (idxInOld > 0) {
17       // idxInOld 位置对应的 vnode 就是需要移动的节点
18       const vnodeToMove = oldChildren[idxInOld]
19       // 不要忘记除移动操作外还应该打补丁
20       patch(vnodeToMove, newStartVNode, container)
21       // 将 vnodeToMove.el 移动到头部节点 oldStartVNode.el 之前,因此使用后者作为锚点
22       insert(vnodeToMove.el, container, oldStartVNode.el)
23       // 由于位置 idxInOld 处的节点所对应的真实 DOM 已经移动到了别处,因此将其设置为 undefined
24       oldChildren[idxInOld] = undefined
25       // 最后更新 newStartIdx 到下一个位置
26       newStartVNode = newChildren[++newStartIdx]
27     }
28   }
29 }

图10-19 新旧两组子节点以及真实DOM节点的状态

10.4 添加新元素

情况一

图10-25 新增节点的情况

图10-26 在旧的一组子节点中找不到可复用的节点

01 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
02   // 增加两个判断分支,如果头尾部节点为 undefined,则说明该节点已经被处理过了,直接跳到下一个位置
03   if (!oldStartVNode) {
04     oldStartVNode = oldChildren[++oldStartIdx]
05   } else if (!oldEndVNode) {
06     oldEndVNode = newChildren[--oldEndIdx]
07   } else if (oldStartVNode.key === newStartVNode.key) {
08     // 省略部分代码
09   } else if (oldEndVNode.key === newEndVNode.key) {
10     // 省略部分代码
11   } else if (oldStartVNode.key === newEndVNode.key) {
12     // 省略部分代码
13   } else if (oldEndVNode.key === newStartVNode.key) {
14     // 省略部分代码
15   } else {
16     const idxInOld = oldChildren.findIndex(
17       node => node.key === newStartVNode.key
18     )
19     if (idxInOld > 0) {
20       const vnodeToMove = oldChildren[idxInOld]
21       patch(vnodeToMove, newStartVNode, container)
22       insert(vnodeToMove.el, container, oldStartVNode.el)
23       oldChildren[idxInOld] = undefined
24     } else {
25       // 将 newStartVNode 作为新节点挂载到头部,使用当前头部节点 oldStartVNode.el 作为锚点
26       patch(null, newStartVNode, container, oldStartVNode.el)
27     }
28     newStartVNode = newChildren[++newStartIdx]
29   }
30 }

图10-27 新旧两组子节点以及真实DOM节点的状态

情况二

图10-28 新旧两组子节点以及真实DOM节点的状态

图10-29 新旧两组子节点以及真实DOM节点的状态

图10-30 新旧两组子节点以及真实DOM节点的状态

图10-31 新旧两组子节点以及真实DOM节点的状态

  • 当更新完毕后,由于变量oldStartIdx的值大于oldEndIdx满足终止条件,因此停止更新。但是节点p-4在更新中被遗漏
01 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
02   // 省略部分代码
03 }
04
05 // 循环结束后检查索引值的情况,
06 if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
07   // 如果满足条件,则说明有新的节点遗留,需要挂载它们
08   for (let i = newStartIdx; i <= newEndIdx; i++) {
09     patch(null, newChildren[i], container, oldStartVNode.el)
10   }
11 }
  • newStartIdxnewEndIdx之间的节点都应该被添加

10.5 移除不存在的元素

图10-32 移除节点的情况

图10-33 新旧两组子节点以及真实DOM节点的状态

图10-34 新旧两组子节点以及真实DOM节点的状态

  • 此时变量newStartIdx的值大于变量newEndIdx的值满足更新停止条件,但是节点中存在未被处理的节点
01 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
02   // 省略部分代码
03 }
04
05 if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
06   // 添加新节点
07   // 省略部分代码
08 } else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
09   // 移除操作
10   for (let i = oldStartIdx; i <= oldEndIdx; i++) {
11     unmount(oldChildren[i])
12   }
13 }
  • oldStartIdxoldEndIdx区域之间的节点都应该被卸载
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值