说说 Real DOM 和 Virtual DOM 的区别?优缺点?

一、是什么

1.1 Real DOM

DOM(Document Object Model)是浏览器对 HTML 文档的编程接口。浏览器解析 HTML 后生成一棵 DOM 树,每个节点都是一个 DOM 对象,包含了大量的属性和方法:

const div = document.createElement("div");
// 一个空 div 元素上有超过 200 个属性
console.log(Object.keys(div).length); // 200+

真实 DOM 节点是重量级对象。每次操作 DOM(创建、修改、删除节点)都可能触发浏览器的重排(Reflow)和重绘(Repaint),这是性能消耗的主要来源。

浏览器渲染一个页面的流程:

  1. 解析 HTML → 构建 DOM 树
  2. 解析 CSS → 构建 CSSOM 树
  3. 合并 DOM 和 CSSOM → 生成 Render 树
  4. 布局(Layout/Reflow)→ 计算每个节点的位置和大小
  5. 绘制(Paint)→ 将像素绘制到屏幕

每次 DOM 变更都可能重新执行第 3-5 步,代价昂贵。

1.2 Virtual DOM

虚拟 DOM 是真实 DOM 的轻量级 JavaScript 对象表示。它是 React 内部使用的一种编程概念,并非浏览器提供的 API。

// 真实 DOM 节点
const realDOM = document.createElement("div");
realDOM.className = "container";
realDOM.appendChild(document.createTextNode("Hello"));

// 对应的虚拟 DOM(简化)
const virtualDOM = {
  type: "div",
  props: {
    className: "container",
    children: "Hello",
  },
  key: null,
  ref: null,
};

虚拟 DOM 对象只包含必要的描述信息,创建和比较的代价远低于真实 DOM 对象。

二、Virtual DOM 的工作原理

2.1 React.createElement

在 React 中,JSX 会被编译为 React.createElement(或 React 17+ 的 jsx 函数)调用,返回一个 ReactElement 对象,即虚拟 DOM 节点:

// JSX
const element = (
  <div className="list">
    <h1>标题</h1>
    <ul>
      <li>项目1</li>
      <li>项目2</li>
    </ul>
  </div>
);

// 编译后(简化)
const element = {
  $$typeof: Symbol.for("react.element"),
  type: "div",
  props: {
    className: "list",
    children: [
      { type: "h1", props: { children: "标题" } },
      {
        type: "ul",
        props: {
          children: [
            { type: "li", props: { children: "项目1" } },
            { type: "li", props: { children: "项目2" } },
          ],
        },
      },
    ],
  },
};

$$typeof 是一个 Symbol 值,用于防止 XSS 攻击——JSON 无法表示 Symbol,因此恶意注入的 JSON 不会被 React 当作合法的 ReactElement 处理。

2.2 Diff 算法

当状态更新时,React 会生成新的虚拟 DOM 树,并与旧树进行比较(Reconciliation)。React 的 diff 算法基于三个假设:

  1. 不同类型的元素会产生不同的树:如果根元素类型不同,直接销毁旧树,创建新树
  2. 同一层级的子元素通过 key 来标识:通过 key 判断哪些元素是相同的,哪些需要移动
  3. 只做同层比较:不会跨层级比较节点
// 情况一:类型不同,整棵子树替换
// 旧树
<div><Counter /></div>
// 新树
<span><Counter /></span>
// → 销毁 div 及其子树,创建 span 及新的 Counter

// 情况二:类型相同,更新属性
// 旧树
<div className="old" title="旧" />
// 新树
<div className="new" title="旧" />
// → 只更新 className 属性

// 情况三:列表使用 key 优化
<ul>
  <li key="a">A</li>
  <li key="b">B</li>
</ul>
// →
<ul>
  <li key="b">B</li>
  <li key="a">A</li>
  <li key="c">C</li>
</ul>
// → 通过 key 识别出 A、B 只需移动位置,C 是新增

2.3 批量更新

React 收集一段时间内的所有状态变更,合并后一次性更新 DOM:

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount((c) => c + 1);
    setCount((c) => c + 1);
    setCount((c) => c + 1);
    // React 只会触发一次重新渲染,最终 count 为 3
  }

  return <button onClick={handleClick}>{count}</button>;
}

这种批处理机制避免了中间状态的无效渲染,是虚拟 DOM 性能优势的重要组成部分。

三、Reconciliation 调和过程

React 的调和过程(Reconciliation)是虚拟 DOM 机制的核心,在 React 16+ 中由 Fiber 架构驱动:

状态变更 → 创建新 ReactElement 树
      → Fiber 调和(Render Phase)
          → 逐节点比较新旧 Fiber
          → 标记需要变更的节点(effectTag)
      → 提交更新(Commit Phase)
          → 执行 DOM 操作
          → 调用生命周期 / useEffect

Render Phase 是可中断的——React 可以暂停低优先级的更新,先处理用户交互等高优先级任务,这就是 React 18 并发渲染的基础。

function App() {
  const [input, setInput] = useState("");
  const [list, setList] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setInput(value); // 高优先级:立即更新输入框

    startTransition(() => {
      setList(generateHugeList(value)); // 低优先级:可中断
    });
  }

  return (
    <div>
      <input value={input} onChange={handleChange} />
      {isPending ? <p>加载中...</p> : <List items={list} />}
    </div>
  );
}

四、Real DOM vs Virtual DOM 对比

维度Real DOMVirtual DOM
本质浏览器原生对象JavaScript 普通对象
重量重量级,200+ 属性轻量级,仅必要描述信息
操作方式命令式(直接操作节点)声明式(描述状态,框架处理)
更新粒度直接操作可精确控制通过 diff 算法计算最小变更
回流重绘每次操作可能触发批量合并后一次性操作
跨平台仅限浏览器可映射到任意渲染目标

4.1 Virtual DOM 的优势

  1. 开发效率高:声明式编程让开发者专注于"是什么"而非"怎么做"
  2. 性能下限高:自动 diff + 批量更新保证了大多数场景下的良好性能
  3. 跨平台能力:虚拟 DOM 是平台无关的抽象层,可以渲染到浏览器、移动端、Canvas 等
  4. 可测试性:虚拟 DOM 是纯 JavaScript 对象,易于测试

4.2 Virtual DOM 的局限

  1. 内存开销:需要额外维护一棵 JavaScript 对象树
  2. 首次渲染不一定快:初始化时要同时创建虚拟 DOM 和真实 DOM
  3. 极端场景性能不如手动优化:手动精确操作 DOM 在特定场景下效率更高

4.3 什么时候 Real DOM 更快?

// 场景:已知只需修改某个元素的文本
// 直接操作 DOM 更高效
document.getElementById("counter").textContent = newCount;

// 而 React 需要:
// 1. 触发组件重新执行
// 2. 生成新的虚拟 DOM 树
// 3. diff 比较
// 4. 最终才更新这一个文本节点

在以下场景中,手动操作 Real DOM 可能更快:

  • 已知精确的更新范围,且更新量极小
  • 高频更新的动画(此时应使用 CSS 动画或 requestAnimationFrame)
  • 大量 DOM 节点的一次性创建(如 innerHTML 一次性设置)

五、面试要点总结

虚拟 DOM 的核心价值不在于"它比真实 DOM 快"这个经常被误解的说法。更准确地说:

虚拟 DOM 提供了一种声明式的编程模型,让开发者不需要手动操作 DOM,同时通过 diff 算法和批量更新机制,保证了在大多数场景下足够好的性能。它是开发体验与性能之间的最优平衡

尤雨溪(Vue 作者)的经典总结也适用于 React:

  • 没有任何框架可以比手动优化的原生 DOM 操作更快
  • 框架的意义在于,不需要手动优化的情况下,依然能提供过得去的性能
  • 虚拟 DOM 真正的价值是声明式 UI + 跨平台渲染的能力

在 React 19 中,React Compiler 的出现进一步证明了虚拟 DOM 架构的优势:编译器可以在构建时自动分析组件依赖,插入记忆化代码,减少不必要的虚拟 DOM 创建和 diff,让这套架构变得更高效。