标准答案

  1. 先比较新旧虚拟节点的类型,类型不同通常直接替换整棵子树。
  2. 类型相同时更新 props,包括新增、修改和删除属性。
  3. 文本节点可以直接比较文本内容。
  4. 子节点可以先做简单的同位置递归比较,再扩展到 key 维度的复用。
  5. 真实 diff 要处理 key、移动、插入、删除、事件、组件边界和异步渲染,不只是递归比较对象。

题目解析

简化版 diff 的目标是讲清“如何把新旧虚拟树的差异变成真实 DOM 操作”。先判断能不能复用当前节点,能复用就更新属性和子节点,不能复用就替换。

key 的意义在列表场景才明显。如果只按位置比较,列表头部插入一个元素可能导致后面节点都被误判为变化;有稳定 key 后,框架可以识别哪些节点只是移动。

面试里不要声称自己能几行代码实现完整 React 或 Vue diff。更好的回答是先给简化模型,再说明真实框架还会结合调度、组件更新、patch flag 和平台差异。

代码示例

一个简化 patch 流程可以先处理类型、属性和同位置子节点。

JavaScript
function patch(oldVNode, newVNode, container) {
  if (!oldVNode) {
    return mount(newVNode, container)
  }

  if (oldVNode.type !== newVNode.type) {
    return replaceNode(oldVNode, newVNode, container)
  }

  const el = newVNode.el = oldVNode.el
  patchProps(el, oldVNode.props, newVNode.props)
  patchChildren(el, oldVNode.children, newVNode.children)

  return el
}

常见误区

  • 把 diff 理解成深度比较两个对象,而不是生成最小或较小成本的 DOM 更新。
  • 列表没有稳定 key,导致复用和移动判断混乱。
  • 忽略事件、组件边界、异步调度和真实框架优化,只讲递归。

作者信息