1098 字
5 分钟
Vue 3 的虚拟 DOM 与 diff 算法

虚拟 DOM 是什么#

VNode(virtual node,虚拟节点)是一个用纯 JS 对象描述真实 DOM 节点的结构。虚拟 DOM 是由这些 VNode 组成的树。

VNode 树相当于一张工程图纸,描述真实 DOM 的结构,但它自己不是真实 DOM。

虚拟 DOM 的作用#

直接操纵真实 DOM 的成本很高。一个空的 div 就自带几百个属性和方法,频繁创建、修改、删除都会付出代价。

虚拟 DOM 的思路是把计算搬到 JS 里:先在 JS 中算出差异,最后一步才去碰真实 DOM。

  • 减少真实 DOM 的操作次数
  • 为 diff 算法提供对比基础
  • 不依赖浏览器环境,可以渲染到小程序、iOS、Android、桌面端

渲染流程#

  1. 模板 <template> 编译成 render 函数
  2. render 执行,生成 VNode 树
  3. patch 函数对比新旧 VNode
  4. 只把变化的部分更新到真实 DOM

一个简化的实现#

下面是教学示意,用来理解流程,不是 Vue 的实现:

// 1. 创建虚拟节点
function h(type, props, children) {
return { type, props, children }
}
// 2. 虚拟 DOM 转真实 DOM
function createDOM(vnode) {
const el = document.createElement(vnode.type)
for (const key in vnode.props) el.setAttribute(key, vnode.props[key])
if (vnode.children) {
vnode.children.forEach((child) => {
el.appendChild(createDOM(child))
})
}
return el
}

对比更新的过程放到下一节。

diff 算法的五个步骤#

diff 要解决的问题是:给定新旧两个子节点数组,算出哪些要删、哪些要增、哪些要移动、哪些不用动。

Vue 3 的 patchKeyedChildren 分成五步。下面用 i 表示起始下标,e1 表示旧数组的结束下标,e2 表示新数组的结束下标。

  1. 从头部同步:i 从 0 开始,新旧节点类型相同时调用 patch 更新并让 i 自增;类型不同就停下。
  2. 从尾部同步:从两个数组的末尾往前比对,类型相同就 patch 更新并让 e1、e2 各减一;不同就停下。
  3. 公共序列加挂载:当 e1 < i <= e2 时,[i, e2] 里全是新节点,逐个挂载到 nextPos(下一个待处理位置)之前。
  4. 公共序列加卸载:当 e2 < i <= e1 时,[i, e1] 里全是待删除的旧节点,逐个卸载。
  5. 未知序列:先为新子节点建立 key → index 映射。再遍历 [i, e1] 的旧节点,能找到 key 就 patch,找不到就卸载,同时记录已见过的最大新索引。有移动时用 getSequence() 求最长递增子序列。最后倒序遍历该区间,挂载新节点,其余节点按需移动。

和第 1、2、3、4 步相比,第 5 步才是通用情况。前面的双端预处理在 Vue 2 里也有,最长递增子序列是 Vue 3 才加的优化。

v-for 与 key#

key 是 diff 识别节点身份的凭据。在 v-for 里尽量写上它,但它是推荐项,不是强制项。

不写 key 时,diff 无法识别节点身份,只能按位置原地复用(in-place patch)。这种默认模式本身开销低,但只适合渲染结果不依赖子组件状态或临时 DOM 状态(比如表单输入值)的场景。索引顺序一变,复选框、输入框的状态就会串。

key 要求是 number | string | symbol,同一父节点下必须唯一,不能用对象。

index 作为 key 的问题#

用 index 作 key 并不违反硬性要求,硬性要求只有上面那两条。但实践中应该避开,原因是它在重排时不提供任何信息。

用 index 作 key 时,列表的 key 序列在重排前后都是 0..n-1,完全相同。diff 找不到移动依据,只能退化成原地复用——效果等于没写 key,列表状态照样会串。用业务上稳定的 ID 才能让 diff 按身份复用和移动节点,避免无谓的销毁重建。

延伸阅读:这篇 B 站视频 讲 Vue 3 diff,属第三方内容,不是 Vue 官方渠道。

第 16 篇,共 19 篇本文属于系列

Vue 3 的虚拟 DOM 与 diff 算法
https://www.choria.top/posts/vue3-vdom-diff/
作者
乔瑞雅·克林威尔
发布于
2026-04-08
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 214 天

部分内容可能已过时