标准答案
- 先比较新旧虚拟节点的类型,类型不同通常直接替换整棵子树。
- 类型相同时更新 props,包括新增、修改和删除属性。
- 文本节点可以直接比较文本内容。
- 子节点可以先做简单的同位置递归比较,再扩展到 key 维度的复用。
- 真实 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,导致复用和移动判断混乱。
- 忽略事件、组件边界、异步调度和真实框架优化,只讲递归。