字数
1449 字
阅读时间
6 分钟
Vue 2 和 Vue 3 的 diff 算法在核心目标(高效更新 DOM)上一致,但实现思路和性能表现有显著差异,主要体现在对比策略、优化方向和细节处理上。以下从原理和差异两方面展开说明:
一、核心原理:diff 算法的本质
diff 算法是虚拟 DOM(VNode)的核心机制,用于对比新旧 VNode 树的差异,只更新变化的部分到真实 DOM,避免全量重渲染。其核心步骤包括:
- 同级对比:只对比同一层级的 VNode(不跨层级对比,降低复杂度)。
- 标签判断:若标签不同,直接销毁旧节点并创建新节点。
- 标签相同:对比属性、文本、子节点等细节差异,局部更新。
二、Vue 2 的 diff 算法
Vue 2 采用双指针遍历对比的策略,核心逻辑基于 Snabbdom 库的实现,复杂度为 O(n²)(极端情况)。
1. 核心流程:
- 同级子节点对比:对新旧子节点数组,用两个指针(旧节点的
startIdx/endIdx和新节点的startIdx/endIdx)从两端向中间遍历。 - 四种命中情况:
- 旧头 vs 新头(索引相同的节点)
- 旧尾 vs 新尾
- 旧头 vs 新尾(节点复用,需移动位置)
- 旧尾 vs 新头(节点复用,需移动位置)
- 未命中情况:
- 若以上四种都不命中,会遍历旧节点列表,用新节点的
key查找可复用的旧节点。 - 若找到,移动节点到对应位置;若未找到,创建新节点。
- 若以上四种都不命中,会遍历旧节点列表,用新节点的
- 处理剩余节点:若新节点有剩余,批量创建;若旧节点有剩余,批量删除。
2. 问题与局限:
- 依赖 key 的准确性:若
key重复或不稳定(如用索引作为key),会导致节点复用错误,引发 DOM 异常。 - 性能瓶颈:当子节点数量庞大(如长列表),双指针遍历和全量查找会导致性能下降(O(n²) 复杂度)。
- 移动逻辑复杂:需要频繁调整指针和节点位置,逻辑冗余。
三、Vue 3 的 diff 算法
Vue 3 对 diff 算法进行了彻底重构,核心优化是引入“最长递增子序列”(LIS),降低了移动节点的次数,复杂度优化为 O(n log n)。
1. 核心流程:
- 前置处理:
- 先对比新旧子节点数组的前缀相同节点(从头部开始,
key和标签都相同),直接复用。 - 再对比后缀相同节点(从尾部开始),直接复用。
- 先对比新旧子节点数组的前缀相同节点(从头部开始,
- 处理中间差异部分:
- 若剩余新节点为空:删除旧节点中剩余的部分。
- 若剩余旧节点为空:创建新节点中剩余的部分。
- 若两者都不为空:
- 用新节点的
key建立映射表(keyToNewIndexMap),快速查找旧节点中可复用的节点。 - 遍历旧节点剩余部分,标记可复用节点的新索引位置,生成
source数组(记录旧节点在新数组中的位置)。 - 计算
source数组的最长递增子序列(LIS):该序列对应的节点无需移动,其他节点基于 LIS 插入到正确位置,减少移动次数。
- 用新节点的
2. 关键优化:
- LIS 减少移动:通过数学方法找到无需移动的节点序列,其他节点只需插入到序列周围,避免了 Vue 2 中频繁的位置调整。
- 哈希表加速查找:用
key映射新节点索引,替代 Vue 2 中的全量遍历,查找效率从 O(n) 提升到 O(1)。 - 边界情况优化:对无
key的节点(如文本节点)单独处理,避免错误复用。 - 复杂度降低:从 O(n²) 优化为 O(n log n),在长列表场景下性能提升显著。
四、核心差异对比
| 维度 | Vue 2 diff 算法 | Vue 3 diff 算法 |
|---|---|---|
| 核心策略 | 双指针从两端向中间遍历 | 前缀/后缀对比 + 最长递增子序列(LIS) |
| 时间复杂度 | O(n²)(极端情况) | O(n log n) |
| 查找效率 | 遍历旧节点查找可复用节点(O(n)) | 哈希表映射(O(1)) |
| 节点移动逻辑 | 依赖指针调整,移动次数较多 | 基于 LIS 减少移动,只插入差异节点 |
| 长列表性能 | 性能较差(大量遍历和移动) | 性能优异(复杂度降低) |
| key 的依赖 | 依赖较强,key 不当易出错 | 优化了无 key 场景,key 作用更稳定 |
五、总结
Vue 3 的 diff 算法通过前缀/后缀快速复用、哈希表加速查找、LIS 减少移动三大优化,解决了 Vue 2 在长列表场景下的性能瓶颈,同时简化了逻辑复杂度。核心差异本质是:Vue 2 侧重“遍历对比”,而 Vue 3 侧重“数学优化 + 哈希加速”,在保持虚拟 DOM 优势的同时,大幅提升了更新效率。