Virtual dom 是 javaScript 对真实 DOM 的轻量级抽象,本质是用普通对象描述 DOM 结构的树形数据结构;它不直接操作浏览器 API,仅作为内存中的“快照”,支持序列化、比较与批量更新,将 DOM 操作从命令式转为声明式。

Virtual DOM 是什么?它不是浏览器原生概念
Virtual DOM 是 javascript 对真实 DOM 的轻量级抽象,本质是一个用普通对象(plain Object)描述 DOM 结构的树形数据结构。它不直接操作浏览器 API,也不挂载到页面上,只是内存中的“快照”。比如 react.createElement('div', { id: 'app' }, 'Hello') 返回的就不是一个
{ type: 'div', props: { id: 'app' }, children: ['Hello'] }
这个对象可序列化、可比较、可批量更新——关键在于它把 DOM 操作从“命令式”变成了“声明式”的中间表示。
为什么 diff + patch 能减少真实 DOM 操作?
浏览器中每次修改 document.body.innerhtml 或调用 element.appendChild() 都会触发样式计算、布局(layout)、绘制(paint),开销大。Virtual DOM 的优化逻辑分两步:
- 新旧 Virtual DOM 树之间做
diff(通常是深度优先、同层比对),只找出真正变化的节点路径,跳过未改动的子树 - 生成最小化的
patch指令集(如INSERT_ELEMENT、UPDATE_TEXT、REMOVE_node),再统一应用到真实 DOM - 避免了逐个属性手动判断是否需要 setAttribute,也绕开了频繁触发重排(reflow)
注意:diff 本身有成本,所以 React 18 后默认启用 concurrent rendering 把 diff 拆成可中断的小任务;vue 3 则用更细粒度的响应式依赖追踪,部分场景甚至跳过 diff。
立即学习“Java免费学习笔记(深入)”;
Virtual DOM 不等于高性能,滥用反而拖慢渲染
很多人误以为用了 Virtual DOM 就自动变快,其实它只在特定条件下生效:
- 组件层级深、局部更新频繁(如聊天列表滚动时只改最后一条消息)——此时 diff 能精准定位,收益明显
- 大量静态内容 + 极少交互(如文档页)——Virtual DOM 的创建和 diff 开销可能超过直接操作 DOM
- 高频动画(60fps)场景(如 canvas 渲染或
requestAnimationFrame驱动的位移)——Virtual DOM 的同步更新模型会成为瓶颈,应绕过它直操作element.style.transform - 服务端渲染(SSR)后做 Hydration 时,如果客户端 Virtual DOM 树与服务端 HTML 结构不一致,会强制丢弃整个 DOM 重新挂载,性能雪崩
没有 Virtual DOM 就不能高效更新?
当然不是。现代框架已有更多元的优化路径:
-
Svelte在编译期就把响应式逻辑转为直接操作 DOM 的语句,运行时零 Virtual DOM -
Preact的 Virtual DOM 实现比 React 更轻,但核心思路一致;Vue 2用的是带 setter 的观察者模式 + 异步队列更新,不依赖完整 VNode 树 diff - 纯 js 场景下,用
document.createDocumentFragment()批量插入、用el.hidden = true替代display: none减少重排,效果立竿见影
真正决定性能的从来不是“有没有 Virtual DOM”,而是“是否避免了不必要的 layout 触发”和“更新是否批量、延迟、可中断”。理解这一点,比记住 diff 算法细节更重要。