面试题库 词条目录
前端Wiki/面试题库/框架与工程化/框架原理

虚拟 DOM 与 Diff 算法

虚拟 DOM 是真实 DOM 的 JS 对象描述({ tag, props, children }),一次 JS 计算远比操作真实 DOM 便宜:框架先 diff 虚拟树得到最小修改集,再一次性 patch 到真实 DOM。它的价值不是“比直接操作 DOM 快”,而是“声明式编程 + 跨平台(RN、SSR) + 批量更新”。

Diff 算法(以 React 为例)三句话:同层比较(不跨层级移动);类型不同直接替换整棵子树;同类型按 key 复用节点,key 是节点身份标识。Vue 的 diff 更进一步:双端比较(头头、尾尾、头尾、尾头四种快速路径)+ 最长递增子序列减少移动。key 用 id 而不是 index 是老生常谈考点:index 做 key 会导致组件状态错配与无谓重渲染。

  • 虚拟 DOM = JS 对象描述视图;价值是声明式、跨平台、批量 patch,而非绝对更快
  • Diff 三规则:同层比较、类型不同整树替换、按 key 复用
  • key 要稳定唯一(用 id);index 做 key 在列表增删时会制造 bug
JS
// 手写 vnode + 简化 diff(面试概念演示)
function h(tag, props, children) {
  return { tag, props, children: children || [] };   // 虚拟节点
}

function patch(parent, oldV, newV, index = 0) {
  const el = parent.children[index];
  // 1. 新节点不存在:删除
  if (!newV) { if (el) parent.removeChild(el); return; }
  // 2. 都不存在:新建
  if (!oldV) { parent.appendChild(render(newV)); return; }
  // 3. 标签不同(或 key 不同):整体替换
  if (oldV.tag !== newV.tag) {
    parent.replaceChild(render(newV), el);
    return;
  }
  // 4. 同标签:更新属性与文本,递归 children
  updateProps(el, oldV.props, newV.props);
  const max = Math.max(oldV.children.length, newV.children.length);
  for (let i = 0; i < max; i++) {
    patch(el, oldV.children[i], newV.children[i], i);
  }
}

function render(v) {
  const el = document.createElement(v.tag);
  Object.entries(v.props || {}).forEach(([k, v]) => (el[k] = v));
  v.children.forEach(c =>
    el.appendChild(typeof c === "string" ? document.createTextNode(c) : render(c)));
  return el;
}
function updateProps() {}  // 省略:diff props 细节

// 验证:列表更新只 patch 差异
const root = document.createElement("ul");
const oldList = h("ul", {}, [h("li", { key: "a" }, ["A"]), h("li", { key: "b" }, ["B"])]);
const newList = h("ul", {}, [h("li", { key: "b" }, ["B"]), h("li", { key: "c" }, ["C"])]);
patch(root, null, oldList);      // 首次渲染
patch(root, oldList, newList);   // 更新:同 key 复用,新增 C,删除 A
console.log("最终 DOM:", root.innerHTML);
本节要点虚拟 DOM 是声明式渲染的中间层;diff 就三规则 + key 复用,key 用稳定 id 不用 index。
🔗 相关词条VueReactJavaScript