Skip to content

Vue diff 算法与 key ​

理解路径 ​

diff 的目标是用最小代价把旧虚拟 DOM 更新成新的。Vue 做同层比较(不跨层)、靠 key 判断节点能否复用。Vue 2 用双端比较;Vue 3 在此基础上加了 patch flag、静态树提升等编译期优化,只对比动态节点,更新更快。

diff 的基本策略 ​

  1. 同层比较:只比较同一层级的节点,不跨层移动,把复杂度从 O(n³) 降到 O(n)。
  2. 类型不同直接替换:标签或组件类型变了,不再深比,直接销毁旧节点、创建新节点。
  3. key 决定能否复用:同层列表里,key 相同才认为是「同一个」节点,尝试就地复用并更新内容。

Vue 2 的 diff 算法 ​

Vue 2 使用经典的 Virtual DOM 加基于深度优先的双端比较算法,主要步骤:

  1. 创建虚拟 DOM 树:组件状态变化时,根据新状态生成新的虚拟 DOM 树(JS 对象表示的抽象层次结构)。
  2. 比较新旧虚拟 DOM 树:通过深度优先算法逐层比较新旧树的节点,找出两者差异。
  3. 生成差异(Diff):发现节点差异时生成差异对象,描述如何更新真实 DOM 以保持与虚拟 DOM 一致。
  4. 应用差异(Patch):把差异对象应用到真实 DOM,如修改节点属性、插入新节点、删除多余节点等。
  5. 更新组件状态:确保虚拟 DOM 与组件状态保持同步。

双端比较的细节:对新旧子节点各设头、尾两个指针,做四种比较(旧头↔新头、旧尾↔新尾、旧头↔新尾、旧尾↔新头),命中就复用并移动指针;四种都不命中,再用 key 建立映射表查找可复用节点。Vue 2 还通过限制比较深度(只比较同层)减小范围,并用 key 机制更准确地追踪列表元素变化。这样能高效处理头尾增删和整体移动,但在处理大型列表时仍有额外优化空间。

Vue 3 的 diff 算法 ​

Vue 3 引入基于 Proxy 的响应式系统,并对 diff 做了多项优化:

  1. Proxy 响应式系统:用 Proxy 替代 Object.defineProperty,拦截更细粒度,能更精确地追踪属性变化。
  2. 静态树提升:提升静态节点、减少比较的复杂性,提高 diff 效率。
  3. Patch Flag:编译期给动态节点打标记(标明节点是否静态、是否有子节点等),运行时只对比有标记的部分,缩小比较范围。
  4. 缓存节点:对一些中间结果进行缓存,避免不必要的重复计算。

源文把 Vue 3 的渲染策略概括为「放弃双端比较、采用更高效的单端比较」,整体流程为:生成新虚拟 DOM → 借助 Patch Flag 快速定位差异 → 生成差异(添加 / 移动 / 删除节点)→ 应用差异(渲染器对 DOM 操作做了优化)→ 更新组件状态。

实际实现中,对带 key 的子节点,Vue 3 会先同步处理头部、尾部相同的节点,再对中间乱序部分建立 key → index 映射,并计算需要保留节点的最长递增子序列,让已相对有序的节点保持不动、只移动最少的节点。配合 patch flag,静态内容直接跳过,所以处理大型组件和复杂视图时更新更快。

key 是什么,有什么作用 ​

key 是 v-for 循环中用于识别每个节点的特殊属性,帮助 Vue 识别每个节点的身份,从而在列表变化时更高效地更新 DOM。key 应具有唯一性,同一列表中不能重复:

html
<div v-for="(item, index) in items" :key="item.id">
  {{ item.name }}
</div>

item.id 唯一时,能帮助 Vue 在更新列表时更准确地识别每个节点。

  • 不写 key(或用 index 当 key):Vue 默认走「就地复用」,按位置复用节点。列表顺序变化或中间增删时可能复用到错误的节点,导致状态错位(输入框内容、勾选状态串到别的行)。
  • 写稳定且唯一的 key:顺序变化时按 key 移动节点,结果正确,通常也更高效。

没有唯一性的 key 怎么办 ​

  1. 优先用数据里的稳定唯一字段,例如 id、编号。

  2. 可以用索引(index)作为 key,但要注意:最好在有唯一标识符的情况下用唯一标识符,因为用索引作为 key 在列表项顺序发生变化时可能引发问题。

    html
    <div v-for="(item, index) in items" :key="index">
      {{ item.name }}
    </div>
  3. 直接迭代对象的值:列表项没有唯一标识符、也不适合用索引时,可考虑用 v-for 直接迭代对象的值而不是索引,让 Vue 用对象的属性名作为 key。

    html
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
  4. 后端没给唯一值时,也可在拿到数据后生成稳定 id(自增、nanoid 等)并存进数据,而不是每次渲染临时生成。

任何情况下都尽量确保 key 具有唯一性,以便 Vue 正确追踪和更新节点。

参考来源 ​