文章

虚拟 DOM 设计思想

虚拟 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 变更量。

本文由作者按照 CC BY 4.0 进行授权