Virtual DOM(虚拟 DOM)
用 JavaScript 对象树描述 UI 结构,通过 diff 算法比较新旧两棵树的差异,最终将最小化的变更批量应用到真实 DOM。React 和 Vue 的核心渲染机制,但 Solid 和 Svelte 已证明可以完全绕过它。
核心概念
Virtual DOM 是一种渲染策略:用轻量的 JavaScript 对象(VNode)描述 UI 的结构和属性,框架维护一棵 VNode 树作为真实 DOM 的"影子副本"。状态变更时,框架生成新的 VNode 树,与旧树做 diff(reconciliation),计算出最小差异集,再将这些差异批量应用到真实 DOM。
核心权衡:用 JS 层面的 diff 计算换取精确的 DOM 操作。diff 本身有成本,但避免了"推倒重来"式的 DOM 重建。
在各框架中的角色
React — VDOM 是核心渲染模型
React 的组件函数每次状态变更都重新执行,返回新的 JSX(即 VNode 描述)。Fiber 架构下 VNode 被映射为 Fiber 节点,reconciler 递归比对新旧 Fiber 树,收集差异,在 commit 阶段批量更新 DOM。
React 的 VDOM diff 是通用但昂贵的兜底机制——因为 useState 不追踪"哪个值影响哪个节点",框架只能通过 diff 来发现变化。React.memo、useMemo 等优化 API 的本质是帮助 diff 跳过不可能变化的子树。
Vue 3 — VDOM + 编译优化
Vue 3 同样使用 VNode 树和 diff,但模板编译器通过 Patch Flags 和 Block Tree 大幅减少了 diff 的工作量。编译器在编译期标记了每个 VNode 的动态部分,运行时 diff 只检查带标记的属性——从 O(模板大小) 降到 O(动态绑定数)。
createElementVNode("div", { class: cls }, "static", 2 /* CLASS */) 中的 2 就是 Patch Flag,告诉 diff 算法"只需要检查 class 属性"。
Solid / Svelte — 无 VDOM
这两个框架证明了 VDOM 不是必需的中间层。通过细粒度响应式(Signal)+ 编译器,状态变更直接映射到具体的 DOM 操作,跳过了 VNode 创建和 diff 阶段。整个渲染管线因此被压缩——从"状态→VNode→diff→DOM"变为"状态→DOM"。
VDOM 的优势和代价
优势:
- 提供了 UI 的声明式描述层——开发者描述"UI 长什么样",框架负责"怎么更新"
- 跨平台渲染的基础——VNode 树可以被渲染到 DOM、Canvas、Native 等不同目标(React Native 的基础)
- 框架可以在 diff 阶段插入调度逻辑——React 的 Fiber 架构利用这一点实现了可中断渲染和优先级调度
代价:
- 内存开销:整棵 VNode 树常驻内存。React 的 Fiber 架构更是双份(current + workInProgress 双缓冲)
- CPU 开销:每次更新都要创建新 VNode + 递归 diff,即使最终只有一个文本节点变了
- 渲染管线变长:多了 VNode 创建和 diff 两个阶段,调度器和性能优化机制都是围绕这个额外管线做的补救
与 Signal 的关系
VDOM 和 Signal 代表了两种根本不同的更新发现策略:
- VDOM:"不知道什么变了,全部重新描述一遍,用 diff 找出差异"——事后发现
- Signal:"精确知道什么变了以及谁依赖它,直接通知"——事前追踪
React Compiler 的出现并没有改变这个本质——它自动化了"跳过 diff"的缓存逻辑,但 VDOM diff 作为兜底机制仍然存在。
引用本术语的文章