在现代前端框架中,虚拟 DOM(Virtual DOM)是提升渲染性能的核心机制之一。而 diff 算法作为虚拟 DOM 更新的关键环节,决定了如何高效地将新旧节点树进行比对,并以最小代价更新真实 DOM。Vue 在不同版本中对 diff 算法进行了演进,理解其原理有助于开发者写出更高效的组件代码。

Vue 中 diff 算法的基本思想
Vue 的 diff 算法并非对整个 DOM 树进行全量比对,而是基于两个核心假设:
- 相同层级的节点类型不同,则无需继续比较子树;
- 跨层级的 DOM 操作极少发生,因此只在同一层级进行比对(即“同层复用”)。
这两个假设将时间复杂度从 O(n³) 降低到 O(n),使得大规模 UI 更新依然保持流畅。Vue 的 diff 过程发生在 patch 阶段,当响应式数据变化触发重新渲染时,会生成新的虚拟节点(VNode),并与旧 VNode 进行比对,最终生成最小化的 DOM 操作指令。
Vue2 的双端 diff 策略
Vue2 采用的是经典的双端指针比对算法(也称“四指针法”),专门用于处理列表(children 数组)的更新。该算法通过维护新旧子节点数组的首尾指针(oldStartIdx、oldEndIdx、newStartIdx、newEndIdx),在每一轮循环中尝试四种可能的匹配:
- 新首 vs 旧首(key 或 tag 相同)→ 复用节点,两首指针右移;
- 新尾 vs 旧尾 → 复用节点,两尾指针左移;
- 新首 vs 旧尾 → 节点需移动到旧首之前;
- 新尾 vs 旧首 → 节点需移动到旧尾之后。
若以上均不匹配,则尝试通过 key 建立映射表(keyToOldIdx)快速查找旧节点中是否存在对应 key 的节点。若找到,则将其移动到正确位置;若未找到,则创建新节点。
这种策略在大多数有序或局部变动的列表场景下表现良好。但若列表频繁逆序或随机打乱,仍可能产生较多移动操作。因此 Vue2 强烈建议为 v-for 提供稳定且唯一的 key,避免使用 index 作为 key,否则在插入/删除操作中可能导致不必要的复用错误和性能下降。
Vue3 的优化:最长递增子序列(LIS)与静态提升
Vue3 在保留同层比对原则的基础上,对 diff 算法进行了多项改进,其中最显著的是引入了最长递增子序列(Longest Increasing Subsequence, LIS)算法来优化节点移动逻辑。
在 Vue3 的 patchChildren 过程中,当进入“未知序列比对”阶段(即无法通过首尾匹配快速处理时),会先遍历新子节点,为每个可复用的节点记录其在旧 children 中的位置索引,形成一个位置数组。随后,通过 LIS 算法找出该数组中最长的递增子序列——这部分节点在旧树中已经是顺序排列的,无需移动。其余节点则根据其目标位置进行插入或移动。
这一优化大幅减少了 DOM 节点的移动次数,尤其在列表顺序发生较大变化时效果显著。例如,将 [A, B, C, D] 变为 [D, C, B, A],传统双端 diff 可能多次移动节点,而 Vue3 能识别出最长递增子序列(如空或单元素),仅对必要节点执行操作。
此外,Vue3 编译器还支持静态节点提升(Static Hoisting)和Block Tree 机制。对于不含动态绑定的静态子树,编译时会被提取为常量,在 diff 时直接跳过比对,进一步减少运行时开销。
实际开发中的最佳实践
- 始终为 v-for 提供唯一 key:key 是 diff 算法正确复用节点的前提。使用 ID 或稳定标识符,而非数组索引。
- 避免在模板中使用复杂表达式:这会阻碍静态提升,增加不必要的动态节点。
- 合理拆分组件:将频繁更新的部分封装为独立组件,利用组件级别的 diff 隔离更新范围。
- 慎用 v-if 与 v-for 同时存在:这可能导致 key 失效或节点类型混乱,影响 diff 效率。
需要强调的是,diff 算法的优化是框架层面的工作,开发者无需手动干预比对过程。但理解其机制有助于避免写出“反模式”代码,从而让 Vue 的自动优化发挥最大效能。