虚拟 DOM 设计思想
一句话概括
虚拟 DOM(Virtual DOM)是一棵用 JS 对象描述的”假 DOM 树”,它作为真实 DOM 和框架之间的缓冲层,通过 diff 算法算出最小变更再一次性应用到页面——用 JS 计算成本换 DOM 操作成本。
核心知识点
1. 虚拟 DOM 的本质——一个 JS 对象
1
2
3
4
5
6
7
8
9
10
// 真实 DOM:<div id="app" class="container"><span>hello</span></div>
// 对应的虚拟 DOM:
const vnode = {
tag: 'div',
props: { id: 'app', class: 'container' },
children: [
{ tag: 'span', props: {}, children: ['hello'] }
]
};
相比真实 DOM 的优势:JS 对象的创建和比较比真实 DOM 操作快 2-3 个数量级。1000 次 JS 对象 diff < 1 次真实 DOM 批量更新。
2. 三大框架的虚拟 DOM 形态
1
2
3
4
5
6
7
8
9
10
11
// Vue:编译时优化 + 运行时虚拟 DOM
// 模板编译阶段就能标记静态节点,diff 时直接跳过
<div>
<span>静态文本</span> // 编译期打标,不参与 diff
<span></span> // 仅此节点参与 diff
</div>
// React:无编译优化,全量 diff
// <span>静态文本</span> 和 <span>{dynamic}</span> 一视同仁
// Svelte:无虚拟 DOM,编译期直接生成精确的 DOM 操作代码
3. h 函数——创建虚拟节点
1
2
3
4
5
6
7
8
9
// Vue 3 的 h 函数
import { h } from 'vue';
const vnode = h('div', { class: 'box' }, [
h('span', null, 'hello'),
h('span', null, 'world')
]);
// 等价模板:<div class="box"><span>hello</span><span>world</span></div>
4. render 函数——VNode → 真实 DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function render(vnode, container) {
// 创建元素
const el = document.createElement(vnode.tag);
// 设置属性
for (const [key, val] of Object.entries(vnode.props || {})) {
el.setAttribute(key, val);
}
// 处理子节点
vnode.children?.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
render(child, el); // 递归
}
});
container.appendChild(el);
}
5. 虚拟 DOM 的价值——不是快,是”对”
1
2
3
4
5
6
7
8
9
10
// 没有虚拟 DOM——手动操作 DOM
el.innerHTML = ''; // 清空
list.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
el.appendChild(li); // 逐个插入
});
// 有虚拟 DOM——框架帮你做
// list.value = newList; // 框架自动 diff,只更新变了的 li
真正的价值:不是比手动优化更快,而是让你不用手动优化,自动达到”还不错”的 DOM 更新效率,同时保持了声明式开发的体验。
其实你每天都在用
- 列表排序:
list.sort()→ diff 算法找到最小移动路径,比innerHTML重绘快十几倍 - v-if 切换:条件从 true 切 false,虚拟 DOM 直接标记卸载——不操作真实 DOM 就能算出”哪些该删”
- v-show 切换:VNode 还在,只是
display变——diff 跳过整块节点 - key 属性:
v-for="item in list" :key="item.id"→ diff 通过 key 复用节点,列表重排只移 DOM 不创建 - Transition 动画:VNode 离开时标记
leaving,等动画跑完才移除真实 DOM
常见误解
❌ 误区:「虚拟 DOM 一定比直接操作 DOM 快」 不是。极限优化场景下,手动
textContent修改可能比走完整 diff 流程更快。虚拟 DOM 的定位是”在声明式开发中提供可接受的性能”,而不是”性能天花板”。❌ 误区:「Vue 3 的虚拟 DOM 和 React 一样」 不一样。Vue 3 有编译期优化——静态提升(hoistStatic)、补丁标志(PatchFlags)、block tree——可以在模板编译阶段标记动态节点,diff 时跳过大量静态比较。React 没有编译期,全依赖运行时 Fiber 调度来分摊 diff。
❌ 误区:「key 是给开发者看的,不影响性能」 恰恰相反,
key是 diff 算法的核心输入。没有 key(或 key 是 index),列表重排时 diff 会”就地复用”导致错误绑定(如 input 内容串位)。key= id 时 diff 能用 O(n) 的最长递增子序列算法找到最小移动路径。❌ 误区:「虚拟 DOM 是 Vue 独有的」 React 最早推广虚拟 DOM(2013),Vue 2 引入,Flutter/NativeScript 也有类似概念,只不过叫法不同。现在这个模式已经成为前端框架的标配思想。
一句话总结
虚拟 DOM 不是银弹,它是一个「用 JS 计算换 DOM 操作」的工程权衡——让你用声明式代码写 UI,框架替你算出最小的 DOM 变更量。