为什么需要 Virtual DOM
直接使用 JavaScript 操作 DOM 是最早期的前端开发方式。通过 document.querySelector 获取元素,再修改其 textContent 等属性来更新页面。这种方式在简单场景下没有问题,但当页面复杂度提升后会面临两个核心瓶颈。
性能瓶颈:重排与重绘
浏览器渲染引擎和 JS 引擎通常共享单线程。短时间大量 DOM 操作会阻塞渲染线程,导致页面卡顿。其中两个关键概念:
| 概念 | 触发条件 | 性能影响 |
|---|---|---|
| Reflow(重排) | DOM 结构变化,如增删节点、修改尺寸 | 高,需重新计算布局 |
| Repaint(重绘) | 元素外观变化,如颜色、文本内容 | 中,需重新绘制像素 |
当 DOM 嵌套层级深、操作频繁时,重排会导致非常大的浏览器开销。通过 Chrome DevTools 的 Performance 面板可以直观观察这些开销——波形图中突出的部分往往就是 HTML 解析和 DOM 操作。
开发体验瓶颈
原生 JS 操作 DOM 代码冗长,特别是嵌套层级深、操作元素多时,维护成本急剧上升。jQuery 时代通过 $ 封装缓解了这一问题,但本质上仍是命令式操作。
Virtual DOM 是什么
Virtual DOM(虚拟 DOM)是使用 JavaScript 对象来描述真实 DOM 结构的轻量级抽象。Vue 会将这个 JS 对象渲染到真实 DOM 上,当对象内容变化时,通过 Diff 算法找出差异,只更新变化的部分。
一个典型的 vnode 对象结构:
{
type: 'h1',
props: {},
children: [
{ type: 'text', children: 'Hello World' }
]
}
javascript
核心特性:
- vnode 本质上是一个纯 JS 对象,不依赖浏览器环境
- 描述了真实 DOM 的结构(标签类型、属性、子节点)
- 通过对比新旧 vnode 对象(Diff 算法),最小化真实 DOM 操作
Vue 与 React 的虚拟 DOM 对比
Vue 和 React 的虚拟 DOM 概念类似,但实现策略有差异:
| 维度 | Vue 3 | React |
|---|---|---|
| 编译时优化 | 侧重编译时标记静态节点,跳过不变部分 | 较少编译时优化 |
| 更新策略 | 细粒度响应式 + 编译时提示 | 自顶向下的 Diff 策略 |
| Patch 标记 | 通过 Block Tree + PatchFlag 标记动态节点 | Fiber 架构 + Lane 优先级调度 |
Vue 3 通过编译器给节点打标签(如 PatchFlag),在更新时可以高效跳过静态节点,这是 Vue 在更新效率上的核心优势。
Vue 三大核心模块
Vue 的渲染管线涉及三个核心模块的协作:
Template(模板)
│
▼ 编译器(Compiler)
│
Render Function(渲染函数)
│
▼ 响应式模块(Reactivity)
│
Virtual DOM(vnode)
│
▼ 渲染器(Renderer)
│
真实 DOM
text
| 模块 | 职责 | 源码位置 |
|---|---|---|
| Reactivity | 创建响应式对象,追踪依赖变化 | packages/reactivity |
| Compiler | 将模板编译为渲染函数 | packages/compiler-core |
| Renderer | 将 vnode 渲染/更新到真实 DOM | packages/runtime-core |
当响应式数据变化时,触发渲染函数重新执行,生成新的 vnode 树,通过 Diff 对比新旧 vnode,最终只将差异更新到真实 DOM。
性能对比:Virtual DOM vs innerHTML
| 维度 | Virtual DOM | innerHTML |
|---|---|---|
| JS 计算 | 创建响应式对象 + Diff 算法 | 拼接 HTML 字符串 |
| DOM 操作 | 只更新必要节点 | 销毁全部旧节点,重建新节点 |
| 性能关联因素 | 数据变化量(变化少则性能好) | 模板大小(模板大则性能差) |
结论:Virtual DOM 的性能与数据变化量正相关。当页面变化少时,Virtual DOM 只需做少量 DOM 操作;而 innerHTML 无论变化多少都需要重建整个 DOM 子树。
相关资源
↑