第 24 题:Virtual DOM 中 diff 算法的核心流程(详细版)
下面是详细讲解(不画图、不引用图片):
24. Virtual DOM 的 Diff 算法核心流程(详细版)
Diff(差异对比)算法是 Virtual DOM 的核心,用来比较新旧两棵虚拟 DOM 树的不同,最终只把差异更新到真实 DOM,从而提升性能。
以下分 6 个步骤讲清楚整个流程:
① 第一层优化:同层比较,不跨层
虚拟 DOM 永不跨层比较:
- 旧树第 1 层只对比新树第 1 层
- 旧树第 2 层只对比新树第 2 层
- ......
原因: 浏览器真实 DOM 结构很少跨层变动,所以避免跨层,大幅减少计算量。
② 第二层优化:节点类型不同 → 整个节点直接替换
判断两个节点是否"同类型",主要看:
| 判断内容 | 示例 |
|---|---|
| 标签是否相同 | div → div |
| 组件是否相同 | MyComponent → MyComponent |
| key 是否一致 | key="1" → key="1" |
如果节点类型不同直接整棵替换:
css
<div> → <span>
不用再继续往下比较 children。
③ 节点类型相同 → 继续比较属性(props)
例如两个 div:
ini
<div id="a" class="x">
<div id="b">
Diff 会:
- 找出删除的属性
- 找出新增的属性
- 找出修改的属性
最终生成类似补丁:
kotlin
PATCH {
remove class
update id: a → b
}
④ 比较 Children(最复杂的部分)
子节点可能会:
- 增加
- 删除
- 移动
- 更新内容
其中列表渲染 v-for 的 diff 最复杂,需要 key。
Vue Diff 对 children 会做三类操作:
(1)同位置比较(前置指针)
从左往右一个一个比:
旧:A B C D
新:A B E D
前两个一致,跳过
到第三个不一致,执行更新
(2)尾部比较(后置指针)
从右往左比:
旧:A B C D
新:E B C D
尾部 D、C、B 都一样
只需处理最前面的 A 、E。
这种前后双指针优化减少很多循环。
(3)核心:key 的最长递增子序列(LIS)优化移动
这是最精华部分。
举例:
旧节点:A B C D
新节点:B A D C
为了尽量减少"移动 DOM 次数",Vue 会:
- 先计算每个旧节点在新列表中的下标映射。
- 找出最长递增子序列(LIS),这些节点不动。
- 剩下的节点才需要移动。
这样 DOM 操作最少,性能最好。
⑤ 生成补丁(patch)列表
patch 列表包含:
- 哪些节点要删除
- 哪些要新增
- 哪些要移动
- 哪些属性要更新
- 哪些文本要替换
最终所有操作都是对真实 DOM 的最小修改集。
⑥ 执行 patch,更新真实 DOM
最后一步由渲染器完成:
- 删除 DOM
- 插入 DOM
- 更新属性
- 设置文本内容
- 移动节点位置
这一步才真正发生浏览器的重绘/回流。
总结(背诵用)
Diff 算法核心 6 步:
- 同层对比,不跨层
- 节点类型不同 → 整个替换
- 节点类型相同 → 更新属性
- 比较 children:前置/后置/Key+LIS
- 生成最小修改补丁(patch)
- 执行 patch 更新真实 DOM
如果你要,我可以继续:
✅ 用图示方式讲
✅ 用代码模拟一次完整的 diff 流程
✅ 用实际 Vue 代码展示 diff 如何触发
你要哪个?