2024-09-18 15:08:22 +08:00

195 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 图解双端diff
>面试题:说一下 Vue3 中的 diff 相较于 Vue2 有什么变化?
- Vue2: 双端diff
- Vue3: 快速diff
**1. diff的概念**
diff 算法是用于比较两棵虚拟 DOM 树的算法,目的是找到它们之间的差异,并根据这些差异高效地更新真实 DOM从而保证页面在数据变化时只进行**最小程度**的 DOM 操作。
思考🤔为什么需要进行diff不是已经有响应式了么
答案:响应式虽然能够侦测到响应式数据的变化,但是只能定位到组件,代表着某一个组件要重新渲染。组件的重新渲染就是重新执行对应的渲染函数,此时就会生成新的虚拟 DOM 树。但是此时我们并不知道新树和旧树具体哪一个节点有区别这个时候就需要diff算法来找到两棵树的区别。
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-06-013616.png" alt="20210301193804" style="zoom: 60%;" />
**2. diff算法的特点**
1. 分层对比:它会逐层对比每个节点和它的子节点,避免全树对比,从而提高效率。
2. 相同层级节点对比:在进行 diff 对比的时候Vue会假设对比的节点是同层级的也就是说不会做跨层的比较。
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-06-013054.png" alt="20210301203350" style="zoom:65%;" />
**3. diff算法详细流程**
1. 从根节点开始比较,看是否**相同**。所谓相同,是指两个虚拟节点的**标签类型**、**key 值**均相同,但 **input 元素还要看 type 属性**
1. 相同
- 相同就说明能够复用此时就会将旧虚拟DOM节点对应的真实DOM赋值给新虚拟DOM节点
- 对比新节点和旧节点的属性如果属性有变化更新到真实DOM. 这说明了即便是对 DOM 进行复用,也不是完全不处理,还是会有一些针对属性变化的处理
- 进入【对比子节点】
2. 不相同
- 如果不同,该节点以及往下的子节点没有意义了,全部卸载
- 直接根据新虚拟DOM节点递归创建真实DOM同时挂载到新虚拟DOM节点
- 销毁旧虚拟DOM对应的真实DOM背后调用的是 vnode.elm.remove( ) 方法
2. 对比子节点:
1. 仍然是同层做对比
2. 深度优先
3. 同层比较时采用的是双端对比
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-06-021144.png" alt="image-20240906101143754" style="zoom:70%;" />
**4. 双端对比**
之所以被称之为双端,是因为有**两个**指针,一个指向头节点,另一个指向尾节点,如下所示:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-13-145148.png" alt="image-20240913225147579" style="zoom:50%;" />
无论是旧的虚拟 DOM 列表,还是新的虚拟 DOM 列表,都是一头一尾两个指针。
接下来进入比较环节,整体的流程为:
1. 步骤一:新头和旧头比较
- 相同:
- 复用 DOM 节点
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021542.png" alt="image-20240914101542039" style="zoom:50%;" />
- 新旧头索引自增
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021629.png" alt="image-20240914101629244" style="zoom:50%;" />
- 重新开始步骤一
- 不相同:进入步骤二
2. 步骤二:新尾和旧尾比较
- 相同:
- 复用 DOM 节点
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021834.png" alt="image-20240914101834010" style="zoom:50%;" />
- 新旧尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021914.png" alt="image-20240914101913347" style="zoom:50%;" />
- 重新开始步骤一
- 不相同,进入步骤三
3. 步骤三:旧头和新尾比较
- 相同:
- 说明可以复用,并且说明节点从头部移动到了尾部,涉及到移动操作,需要将旧头对应的 DOM 节点移动到旧尾对应的 DOM 节点之后
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021232.png" alt="image-20240914101231300" style="zoom:50%;" />
- 旧头索引自增,新尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-021401.png" alt="image-20240914101400686" style="zoom:50%;" />
- 重新开始步骤一
- 不相同,进入步骤四
4. 步骤四:新头和旧尾比较
- 相同:
- 说明可以复用,并且说明节点从尾部移动到了头部,仍然涉及到移动操作,需要将旧尾对应的 DOM 元素移动到旧头对应的 DOM 节点之前
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-025559.png" alt="image-20240914105559210" style="zoom:50%;" />
- 新头索引自增,旧尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-025649.png" alt="image-20240914105649208" style="zoom:50%;" />
- 重新开始步骤一
- 不相同:进入步骤五
5. 暴力比较:上面 4 个步骤都没找到相同的,则采取暴力比较。在旧节点列表中寻找是否有和新节点相同的节点,
- 找到
- 说明是一个需要移动的节点,将其对应的 DOM 节点移动到旧头对应的 DOM 节点之前
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-030013.png" alt="image-20240914110012627" style="zoom:50%;" />
- 新头索引自增
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-030048.png" alt="image-20240914110048026" style="zoom:50%;" />
- 回到步骤一
- 没找到
- 说明是一个新的节点,创建新的 DOM 节点,插入到旧头对应的 DOM 节点之前
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-030333.png" alt="image-20240914110332605" style="zoom:50%;" />
- 新头索引自增
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-030401.png" alt="image-20240914110401233" style="zoom:50%;" />
- 回到步骤一
新旧节点列表任意一个遍历结束,也就是 oldStart > OldEnd 或者 newStart > newEnd 的时候diff 比较结束。
- 旧节点列表有剩余newStart > newEnd对应的旧 DOM 节点全部删除掉
- 新节点列表有剩余oldStart > OldEnd将新节点列表中剩余的节点创建对应的 DOM放置于新头节点对应的 DOM 节点后面
**综合示例**
当前旧 Vnode 和新 VNode 如下图所示:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-031038.png" alt="image-20240914111038061" style="zoom:50%;" />
1. 头头对比,能够复用,新旧头指针右移
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-031750.png" alt="image-20240914111750328" style="zoom:50%;" />
2. 头头不同,尾尾相同,能够复用,尾尾指针左移
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-031936.png" alt="image-20240914111936261" style="zoom:50%;" />
3. 头头不同尾尾不同旧头新尾相同旧头对应的真实DOM移动到旧尾对应的真实DOM之后旧头索引自增新尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-032233.png" alt="image-20240914112233100" style="zoom:50%;" />
4. 头头不同尾尾不同旧头新尾不同新头旧尾相同旧尾对应的真实DOM移动到旧头对应的真实DOM之前新头索引自增旧尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-032710.png" alt="image-20240914112710405" style="zoom:50%;" />
5. 头头不同尾尾不同旧头新尾不同新头旧尾不同进入暴力对比找到对应节点将对应的真实DOM移动到旧头对应的真实DOM之间新头索引自增
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-033001.png" alt="image-20240914113000896" style="zoom:50%;" />
6. 头头不同尾尾不同旧头新尾不同新头旧尾相同将旧尾对应的真实DOM移动到旧头对应的真实DOM之前新头索引自增旧尾索引自减
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-033248.png" alt="image-20240914113247844" style="zoom:50%;" />
7. 头头不同尾尾不同旧头新尾不同新头旧尾不同暴力对比发现也没找到说明是一个全新的节点创建新的DOM节点插入到旧头对应的DOM节点之前新头索引自增
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-033445.png" alt="image-20240914113444878" style="zoom:50%;" />
8. newEnd > newStartdiff 比对结束,旧 VNode 列表还有剩余,直接删除即可。
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2024-09-14-033722.png" alt="image-20240914113721337" style="zoom:50%;" />
---
-EOF-