虚拟 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。


相关推荐
斟的是酒中桃9 分钟前
基于Transformer的智能对话系统:FastAPI后端与Streamlit前端实现
前端·transformer·fastapi
烛阴27 分钟前
Fract - Grid
前端·webgl
JiaLin_Denny42 分钟前
React 实现人员列表多选、全选与取消全选功能
前端·react.js·人员列表选择·人员选择·人员多选全选·通讯录人员选择
brzhang1 小时前
我见过了太多做智能音箱做成智障音箱的例子了,今天我就来说说如何做意图识别
前端·后端·架构
为什么名字不能重复呢?1 小时前
Day1||Vue指令学习
前端·vue.js·学习
eternalless2 小时前
【原创】中后台前端架构思路 - 组件库(1)
前端·react.js·架构
Moment2 小时前
基于 Tiptap + Yjs + Hocuspocus 的富文本协同项目,期待你的参与 😍😍😍
前端·javascript·react.js
Krorainas2 小时前
HTML 页面禁止缩放功能
前端·javascript·html
whhhhhhhhhw3 小时前
Vue3.6 无虚拟DOM模式
前端·javascript·vue.js
鱼樱前端3 小时前
rust基础(一)
前端·rust