前端 & AI 面试题库
React

虚拟 DOM 与 Diff 算法

React 中的虚拟 DOM 和 Diff 算法是什么?

核心答案

**虚拟 DOM(Virtual DOM)**是 React 的核心概念,它是一个 JavaScript 对象,用来描述真实 DOM 的结构。Diff 算法是 React 用来比较新旧虚拟 DOM,找出最小更新差异的算法。

虚拟 DOM

定义:虚拟 DOM 是真实 DOM 的 JavaScript 表示,是一个轻量级的 JavaScript 对象树。

结构

// 真实 DOM
<div id="app">
  <h1>Hello</h1>
  <p>World</p>
</div>

// 虚拟 DOM(简化表示)
{
  type: 'div',
  props: {
    id: 'app',
    children: [
      {
        type: 'h1',
        props: { children: 'Hello' }
      },
      {
        type: 'p',
        props: { children: 'World' }
      }
    ]
  }
}

优势

  1. 性能优化:避免直接操作 DOM,减少重排和重绘
  2. 跨平台:可以渲染到不同平台(Web、Native、Canvas)
  3. 声明式编程:描述 UI 应该是什么样子,而不是如何操作
  4. 批量更新:合并多个更新,减少渲染次数

Diff 算法

核心思想:React 的 Diff 算法基于三个假设:

  1. 不同类型的元素会产生不同的树:如果根节点类型不同,直接替换整个树
  2. 相同类型的元素可以复用:只更新变化的属性
  3. 通过 key 标识元素:key 帮助 React 识别哪些元素改变了

Diff 策略

1. 树对比(Tree Diff)

  • 从根节点开始对比
  • 如果节点类型不同,直接替换整个子树
  • 如果节点类型相同,继续对比子节点

2. 组件对比(Component Diff)

  • 相同类型的组件:更新 props,继续递归对比
  • 不同类型的组件:直接替换整个组件树

3. 元素对比(Element Diff)

  • 相同类型的元素:只更新变化的属性
  • 不同类型的元素:替换整个元素

4. 列表对比(List Diff)

  • 使用 key 标识元素
  • 通过 key 快速定位变化
  • 最小化 DOM 操作

Diff 算法示例

场景1:节点类型不同

// 旧虚拟 DOM
<div>
  <Counter />
</div>

// 新虚拟 DOM
<span>
  <Counter />
</span>

// Diff 结果:直接替换整个 div 为 span

场景2:属性变化

// 旧虚拟 DOM
<div className="old" id="app">Hello</div>

// 新虚拟 DOM
<div className="new" id="app">Hello</div>

// Diff 结果:只更新 className 属性

场景3:列表变化(无 key)

// 旧虚拟 DOM
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

// 新虚拟 DOM(在开头插入)
<ul>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

// Diff 结果:所有 li 都需要更新(性能差)

场景4:列表变化(有 key)

// 旧虚拟 DOM
<ul>
  <li key="1">1</li>
  <li key="2">2</li>
  <li key="3">3</li>
</ul>

// 新虚拟 DOM(在开头插入)
<ul>
  <li key="0">0</li>
  <li key="1">1</li>
  <li key="2">2</li>
  <li key="3">3</li>
</ul>

// Diff 结果:只插入新的 li,其他复用(性能好)

延伸追问

1. 为什么需要虚拟 DOM?

回答:原因:

1. 性能优化

// 直接操作 DOM(慢)
for (let i = 0; i < 1000; i++) {
  document.getElementById('list').innerHTML += `<li>${i}</li>`;
}

// 使用虚拟 DOM(快)
const items = Array.from({ length: 1000 }, (_, i) => i);
const vdom = <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>;
ReactDOM.render(vdom, document.getElementById('root'));

2. 批量更新

  • React 会将多个状态更新合并
  • 只进行一次 DOM 更新
  • 减少重排和重绘

3. 跨平台

  • 虚拟 DOM 是 JavaScript 对象
  • 可以渲染到 Web、Native、Canvas 等平台
  • 一套代码,多端运行

4. 声明式编程

  • 描述 UI 应该是什么样子
  • 不需要关心如何操作 DOM
  • 代码更易维护

2. Diff 算法的时间复杂度?

回答:复杂度分析:

传统 Diff 算法:O(n³)

  • 需要比较所有节点对
  • 计算最小编辑距离

React Diff 算法:O(n)

  • 基于三个假设优化
  • 只比较同一层级的节点
  • 通过 key 快速定位

优化策略

  1. 同层比较:只比较同一层级的节点
  2. 类型判断:类型不同直接替换
  3. key 优化:快速定位变化

3. 为什么 key 不能使用索引?

回答:问题示例:

使用索引作为 key

// 初始列表
const list = ['A', 'B', 'C'];
// key: 0, 1, 2

// 删除第一个元素后
const list = ['B', 'C'];
// key: 0, 1(但对应的是 B 和 C,不是原来的 B 和 C)

// React 认为:
// key 0: A → B(更新)
// key 1: B → C(更新)
// key 2: C → 删除

// 实际应该是:
// A 删除
// B、C 保持不变

问题

  • 导致不必要的更新
  • 状态可能错乱
  • 性能下降

正确做法

// 使用唯一 ID
const list = [
  { id: 1, name: 'A' },
  { id: 2, name: 'B' },
  { id: 3, name: 'C' }
];

// key={item.id}

4. 虚拟 DOM 一定比直接操作 DOM 快吗?

回答:不一定:

虚拟 DOM 的优势

  • 批量更新
  • 减少不必要的 DOM 操作
  • 跨平台

直接操作 DOM 的优势

  • 没有虚拟 DOM 的开销
  • 对于简单操作可能更快

结论

  • 复杂应用:虚拟 DOM 更快(批量更新、优化)
  • 简单操作:直接操作 DOM 可能更快
  • React 的优势:不只是性能,还有开发体验、可维护性

5. React Fiber 和虚拟 DOM 的关系?

回答:关系:

虚拟 DOM

  • 描述 UI 的数据结构
  • 定义"UI 是什么样子"

Fiber

  • 虚拟 DOM 的增强版本
  • 增加了调度信息(优先级、状态等)
  • 定义"如何高效渲染 UI"

关系

虚拟 DOM → Fiber 节点 → 渲染

Fiber 的增强

  • 增加了优先级
  • 支持可中断渲染
  • 支持并发更新
  • 更好的性能优化

(注:文档部分内容可能由 AI 生成)