虚拟 DOM 到底是啥?为什么 React 要用它?

在学习 React 的过程中,虚拟 DOM(Virtual DOM) 是绕不过去的核心概念。

很多面试也会经常问:

"什么是虚拟 DOM?它解决了什么问题?有什么优点?"

然而,很多人停留在一句:

虚拟 DOM 就是用 JavaScript 对象来描述真实 DOM 结构。

这种说法虽然没错,但过于表面,容易把虚拟 DOMReact 的实现细节混为一谈。

今天我们带着下面几个问题,来彻底理清Virtual DOM

  1. ✅ 什么是虚拟 DOM
  2. ✅ 为什么需要虚拟 DOM
  3. ✅ 在 React 中是如何落地实现的
  4. ✅ 虚拟 DOM 的优势与多平台渲染

🌱 1. 什么是虚拟 DOM?

虚拟 DOM(Virtual DOM)并不是一个新数据结构,而是一种编程思想。

它的核心理念是:把 UI 的状态保存为一种理想化(或者说抽象化、虚拟化)的结构,然后映射(render)成实际平台的渲染接口。

在 React 的官方 FAQ 里,有一句非常准确的定义:

The Virtual DOM is a concept where a virtual representation of the UI is kept in memory. (虚拟 DOM 是一种概念,指将 UI 的一种理想化表示保存在内存中。)

这就意味着:

  • 虚拟 DOM 其实就是一种用来描述界面层次结构的中间层。

  • 至于这个"虚拟结构"用什么来描述?可以是:

    • JS 对象(React 的做法)
    • JSON(某些轻量框架)
    • 或者任何抽象数据结构

在 React 中,恰好选择用 JavaScript 对象来承载这种虚拟树。

所以:

虚拟 DOM 是一种思想,js对象只是 React 中具体的实现手段。


📝 2. 为什么需要虚拟 DOM?

JS 计算更快

浏览器中,DOM 是浏览器引擎管理的,并且为了渲染到屏幕,要经历:

  • 解析 HTML → 构建 DOM Tree → Style → Layout → Paint → Composite
  • 每次 DOM 的读写(尤其是触发重排/回流)都比较昂贵。

(1) 创建10000个js对象 vs 创建10000 DOM

js 复制代码
   // 计时开始
  console.time();
  const arr = [];
  // 创建 10000 个 DOM 对象
  for (let i = 0; i < 10000; i++) {
    arr.push(document.createElement("div"));
  }
  // 计时结束 平均在 2.5ms 左右
  console.timeEnd(); 

  // 计时开始
  console.time();
  const list = [];
  // 创建 10000 JS 对象
  for(let i=0; i<10000; i++){
    list.push({
        tag : 'div'
    });
  }
  // 计时结束 平均是在 0.4ms 左右
  console.timeEnd(); 

因此:可以看出操作js对象的时间和操作DOM对象的时间是完全不一样的,js层面的计算速度要远高于DOM层面的计算速度。


(2)更新阶段的显著优势

那么此时有一个问题

最终还不是要把虚拟 DOM 转成真实 DOM,那不是多此一举吗?

其实:

  • 在首次渲染时,虚拟 DOM 反而更慢一点(多了一层虚拟树)。

  • 但在 更新阶段,虚拟 DOM 可以:

    • 比较新旧虚拟 DOM 树(Diff)
    • 只把必要的变化同步到真实 DOM
  • 避免直接用 innerHTML 或大批量直接操作 DOM,导致整个节点都销毁再重建。

场景 innerHTML 虚拟 DOM
首次渲染 直接创建真实 DOM 先转虚拟 DOM,再创建真实 DOM
更新 UI 直接销毁/重建所有节点 Diff 算法后最小化 DOM 修改
性能消耗主要在 DOM 解析与重绘 JS 对象对比,避免多余 DOM 变更

(3)跨平台渲染抽象

虚拟 DOM 不仅仅是为了浏览器,因为它只是一个抽象的 UI 描述,所以可以:

  • 在不同宿主环境下映射不同渲染引擎:

    • 浏览器用 ReactDOM 渲染成真实 DOM
    • 原生应用用 React Native 渲染成原生组件
    • Canvas / SVG 使用 React ART
    • Node 里可以 ReactDOMServer 输出 HTML
    • React Test Renderer 用来生成 JSON,做快照测试

通过这一层抽象,React 实现了真正的:

UI = f(state) 状态发生变化,React 重新计算虚拟 DOM,然后只把变化同步到真实宿主。


👀 3. React 中虚拟 DOM 的实现

在 React 中通过 JSX 来描述 UI,JSX 最终会被转为一个叫做 createElement 方法的调用,调用该方法后就会得到虚拟 DOM 对象。

jsx 复制代码
<h1 id="title">Hello</h1>

JSX 只是一个语法糖,最终会被 Babel 编译成:

jsx 复制代码
React.createElement('h1', { id: 'title' }, 'Hello')

在 React 源码里,对应如下:

jsx 复制代码
/**
 *
 * @param {*} type 元素类型 div
 * @param {*} config 属性对象 {id : "title"}
 * @param {*} children 子元素 hello
 * @returns
 * <div id="title">hello</div>
 */
export function createElement(type, config, children) {
  let propName;

  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    // ...
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  // 经历了上面的 if 之后,所有的属性都放到了 props 对象上面
  // props ==> {id : "title"}

  // children 可以有多个参数,这些参数被转移到新分配的 props 对象上
  // 如果是多个子元素,对应的是一个数组
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    // ...
    props.children = childArray;
  }

  // 添加默认的 props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  );
}

const ReactElement = function (type, key, ref, self, source, owner, props) {
    // 该对象就是最终向外部返回的 vdom(也就是用来描述 DOM 层次结构的 JS 对象)
  const element = {
    // 让我们能够唯一地将其标识为 React 元素
    $$typeof: REACT_ELEMENT_TYPE,
    // 元素的内置属性
    type: type,
    key: key,
    ref: ref,
    props: props,
    // 记录负责创建此元素的组件。
    _owner: owner,
  };
  // ...
  return element;
};

💡 最终ReactElement方法返回的element对象就是我们所说的虚拟DOM对象,所以在 React 中,虚拟 DOM ≈ React Element(JS 对象)。


🚀 4. 虚拟 DOM 的更新原理(Diff)

当状态发生变化时:

  1. React 会重新执行函数组件,生成新的虚拟 DOM 树。
  2. 用 Diff 算法(基于一些优化规则,比如同层比较、用 key 匹配)找到哪些节点需要更新。
  3. 只对比出来不同的部分进行 DOM 操作。

这样就避免了:

  • innerHTML 直接替换全部
  • 频繁操作 DOM,导致多次回流、重绘

🏆 5. 虚拟 DOM 的优势小结

JS 端快

  • 大部分 diff、计算在 JS 进行,避免频繁操作 DOM。

减少不必要的 DOM 变更

  • 使用 diff 算法精准找到需要更新的节点。

跨平台渲染能力

  • 同一套逻辑,支持浏览器、Native、Canvas、Server 端。

更好的可维护性

  • UI = f(state),避免直接操作 DOM,减少副作用。

💬 6. 面试高分回答参考

📝 什么是虚拟 DOM?它的优点有哪些?

回答示例:

虚拟 DOM 本质上是一种编程思想,是用 JavaScript 对象在内存中对真实 DOM 结构进行抽象描述。在 React 中,使用 createElement 返回的就是虚拟 DOM,也叫 React Element。

使用虚拟 DOM 有以下优点:

  • 避免直接操作 DOM,主要在 JS 内存中 diff,比操作真实 DOM 更快。
  • 通过 Diff 算法精确找出需要更新的节点,减少浏览器重排和重绘。
  • 能作为跨平台渲染的中间层,不仅可以渲染到浏览器 DOM,还能渲染到原生组件(React Native)、Canvas(React ART)。
  • 虚拟 DOM 发挥优势的时机主要体现在更新的时候,相比较 innerHTML 要将已有的 DOM 节点全部销毁,虚拟 DOM 能够做到针对 DOM 节点做最小程度的修改

因此虚拟 DOM 带来了更高的性能潜力与更强的可维护性。


🎉 7. 小结

✅ 虚拟 DOM 并不是 React 的专利,也并非一定要用 JS 对象,只是一种 UI 抽象化、函数化描述的思想。

✅ React 的虚拟 DOM 是这种思想的一个经典实现,它结合 Diff 算法,帮助我们在不同宿主环境下快速、精准地更新 UI。


相关推荐
JeffongTan1 小时前
在LWC中镶嵌VF Page获取用户IP
前端·javascript·salesforce
不cong明的亚子4 小时前
web live多屏互动平台
javascript·经验分享·音视频
陆枫Larry4 小时前
Astro 是什么
前端
是立不是利5 小时前
写给设计师的 CSS 博客美学指南
前端·css
dsyyyyy11016 小时前
JS复习,难点,易错点总结
开发语言·javascript·ecmascript
默_笙6 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师6 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端
kyriewen6 小时前
我手写了个极简版 React Router——才搞懂 v6 为什么砍了这么多 API
前端·javascript·程序员
a1117766 小时前
Shirone 二次元博客主题 开源
前端·开源
IT_陈寒6 小时前
Python的切片赋值把我坑惨了,这不是bug是特性
前端·人工智能·后端