面试题库 词条目录
前端Wiki/词条/虚拟 DOM 与 Diff

虚拟 DOM 与 Diff

定义用 JS 对象描述真实 DOM 结构,更新时先对新旧虚拟树 diff 得最小修改再真实操作 DOM

直接操作 DOM 的代价:每次修改都可能引发回流重绘,且代码里「如何改」散落一地。虚拟 DOM(一层 JS 对象描述)把「改 DOM」拆成两步:① 生成新虚拟树与旧树做 diff 得到差异补丁(patch);② 一次性把补丁应用到真实 DOM。中间多一层抽象,换来「声明式数据驱动」的编程模型与跨平台能力(React Native、SSR)。

Diff 的三大假设让算法从 O(n³) 降到 O(n):① 同层比较(不跨层级移动);② 类型不同直接替换整个子树(标签变了重建);③ 列表靠 key 标识复用(key 是身份标识不是索引)。新节点树上每个节点位置稳定,用 key 判断「是复用还是新建删除」——这是没有 key 或用 index 做 key 重渲染出错的根本原因。

JavaScript
// 虚拟 DOM 长什么样
const vnode = {
  tag: 'ul',
  props: { class: 'list' },
  children: [
    { tag: 'li', props: { key: 'a' }, children: ['第 1 项'] },
    { tag: 'li', props: { key: 'b' }, children: ['第 2 项'] },
  ],
};

// diff 的核心判定
function diff(oldVn, newVn) {
  if (oldVn.tag !== newVn.tag) {
    return { type: 'replace', vnode: newVn };      // 标签不同 → 整树替换
  }
  if (oldVn === newVn) return { type: 'skip' };    // 同引用 → 跳过
  // ... props diff、children diff(列表用 key 复用)
}

// 列表 key 的价值(为什么 index 做 key 不行):
//   [A, B, C] → [C, A, B](C 插队到前面)
//   有 key:移动 C 的真实节点(复用,顺序按新数组调整)✓
//   无 key/index:按位置逐个 diff → A 被改成 C、B 改成 A…原地更新
//   后果:输入框等带状态的子组件状态错乱 + 全部节点重绘
⚠ 常见误区
🔗 相关词条重绘与回流Fiber 架构Vue 与 React 对比
← 返回来源章节虚拟 DOM 与 Diff 算法