虚拟 dom 的 diff 逻辑基于同层级、带 key 的结构语义比对,时间复杂度 o(n),分步判定 tagname/key/props/children,输出确定性补丁清单,确保渲染可预测、可复现、跨平台一致。

虚拟 DOM 的 Diff 逻辑不是“逐像素比对”,而是基于结构语义的增量推导;它的可预测性,来自明确的比较规则和确定性的更新路径——不是靠猜,而是靠约束。
Diff 只在同层级做对比
算法不会跨层查找匹配节点,比如旧树中 div > ul > li 的某个 li,不会去新树的 section > ol > li 里找对应项。它只在同一深度上,按顺序依次比对子节点。这带来两个实际效果:
- 时间复杂度控制在 O(n),避免 O(n³) 级别的全量穷举
- 行为稳定:只要 JSX 结构层级不变,diff 走哪条分支、产生哪些 DOM 操作,每次都能复现
key 是节点身份的唯一锚点
没有 key 时,框架只能按索引顺序硬匹配:第 0 个对第 0 个,第 1 个对第 1 个……一旦插入或删除中间项,后续所有节点都会被误判为“已变更”,触发不必要重建。
加上 key 后,diff 过程变成“查表匹配”:
- 旧树中所有带 key 的节点,先登记进一个映射表(如
{'item-123': vnode, 'item-456': vnode}) - 遍历新树节点时,直接用 key 查旧表,命中即复用,未命中则新建,多余项则卸载
- 移动操作也被识别出来:同一个 key 出现在不同索引位置,说明是位移,而非删+增
属性与子节点分层判定
Diff 不是一次性决定“整个节点要不要换”,而是分步决策:
- 先比 tagName 和 key:两者都一致 → 复用该 DOM 节点,进入属性/子节点比对;任一不同 → 直接替换整块
- 再比 props(class、style、事件监听器等):只更新真正变化的属性,不覆盖未变字段
- 最后递归比 children:同样遵循同层 + key 规则,形成树状收敛
可预测性体现在渲染结果与副作用分离
虚拟 DOM 的 diff 过程本身不执行副作用(比如不调用 useEffect、不触发 ref 回调),它只输出一份“DOM 补丁清单”(例如:[修改 div.style.color, 移动 li 元素, 删除 span])。这份清单是纯数据、可序列化、可回放的。
这意味着:
- 同一组输入(相同 state + props + key),永远生成相同的补丁清单
- 你可以在服务端预计算 diff,也可以在测试中断言“点击后应触发哪几项 DOM 修改”
- 即使渲染器换为 React Native 或 Canvas,只要输入一致,diff 逻辑不变,只是补丁解释方式不同










