虚拟 DOM 的核心价值是减少无效 DOM 操作:通过 diff 算法识别最小变更并批量 patch,而非提升创建速度;它本质是描述 DOM 的 JS 对象树,不直接操作真实 DOM。

虚拟 DOM 不是性能银弹,它真正起作用的地方是「减少无效 DOM 操作」——只要真实 DOM 更新次数变少、范围变小,渲染就快。但如果你直接用 innerHTML 批量写入,或在 Vue/React 里滥用 v-html / dangerouslySetInnerHTML,虚拟 DOM 完全不生效。
虚拟 DOM 是什么:一棵用 JS 对象描述的 DOM 树
它不是 DOM,也不是字符串,而是一组嵌套的普通 JS 对象,每个对象描述一个元素的类型、属性、子节点等。比如:
const vnode = {
tag: 'div',
props: { className: 'container' },
children: [
{ tag: 'span', children: ['Hello'] }
]
}
这个结构可被框架用来和上一次的 vnode 做对比(diff),而不是每次都操作真实 DOM。
它怎么提升性能:靠 diff + 批量 patch,不是靠“快”
关键不在“构建虚拟 DOM 快”,而在“只更新变化的部分”。真实 DOM 操作昂贵,尤其是触发重排(reflow)时。虚拟 DOM 的优化逻辑集中在:
立即学习“Java免费学习笔记(深入)”;
- diff 过程跳过静态节点(如 Vue 的
v-once、React 的React.memo) - 同层比对(不跨层级移动节点),避免 O(n³) 算法
- 把多次
setState或响应式更新合并成一次 patch,减少强制同步渲染
注意:React.memo 和 shouldComponentUpdate 是手动干预 diff 的入口,不加它们,哪怕组件没变也会走一遍 diff。
什么时候它反而拖慢性能
虚拟 DOM 有开销:创建对象、递归 diff、生成 patch 列表。以下场景容易翻车:
- 超长列表(>1000 项)不做
windowing(如react-window),每次滚动都 diff 全量 vnode - 在
render函数里写new Date()、JSON.parse()等副作用操作,导致无意义重计算 - 用
key乱设(比如用Math.random()),让 diff 认为所有节点都需替换 - 在 Vue 中对大型响应式对象频繁赋值
obj.a = ...; obj.b = ...;,触发多次依赖收集 + diff
原生 JS 也能模拟,但别为了“学原理”在项目里手写
你可以用几行代码写出最简虚拟 DOM 渲染器,但它缺编译优化、服务端渲染支持、Suspense、错误边界等生产必需能力。比如:
function createElement(tag, props, children) {
return { tag, props, children };
}
function render(vnode, container) {
const el = document.createElement(vnode.tag);
if (vnode.props) Object.entries(vnode.props).forEach(([k, v]) => el.setAttribute(k, v));
if (vnode.children) vnode.children.forEach(child => {
el.appendChild(typeof child === 'string' ? document.createTextNode(child) : render(child, null));
});
if (container) container.appendChild(el);
return el;
}
这种实现连事件绑定、属性更新、diff 都没有。真要优化性能,优先看 requestIdleCallback 分片更新、IntersectionObserver 懒加载、CSS will-change 提前提示渲染器,而不是纠结 virtual DOM 本身。
真正容易被忽略的点是:虚拟 DOM 的收益高度依赖框架的更新调度策略。React 的 Concurrent Mode 和 Vue 3 的 Proxy + 编译时静态提升 才是现代性能差异的分水岭,不是 vnode 数据结构本身。











