深度优先遍历(dfs)是虚拟dom patch过程的主干控制流,从根节点开始递归访问每个节点,在访问点同步完成比对、差异记录与补丁生成,最终按dfs顺序批量提交真实dom更新。

虚拟 DOM 树的深度优先遍历(DFS)是 diff 算法的基础执行方式,它决定节点比对的顺序和时机。Patch 过程本身不单独“按 DFS 顺序执行”,而是以 DFS 遍历为骨架,在每个访问到的节点上同步完成“比较 → 记录差异 → 生成更新操作”的闭环。换句话说,DFS 是贯穿 patch 全流程的控制流逻辑。
DFS 遍历是 patch 的主干路径
在 Vue、React(早期)、以及多数轻量级虚拟 DOM 实现中,patch 的起点是两棵虚拟 DOM 树的根节点。算法从根开始,先处理当前节点,再递归处理其第一个子节点,直到叶子节点;回溯时再处理兄弟节点——这正是典型的先序遍历(根→左→右)。整个过程不依赖队列或层级缓存,仅靠函数调用栈推进。
- 每进入一个节点,立即做三件事:判断是否可复用(比对 tag 和 key)、比对属性(props / attrs)、准备子节点比对
- 只有当前节点的子节点列表确定需要更新,才会递归调用 patch 处理子树;否则跳过该分支
- 真实 DOM 更新操作(如 setAttribute、appendChild、removeChild)不是在遍历时立即执行,而是收集为“补丁对象”(patch object),最后统一提交——但收集顺序严格遵循 DFS 走向
节点比对发生在 DFS 的每个访问点
深度优先不是只“走一遍”,而是在每个访问节点上触发完整的局部 diff:
- 类型不匹配(如 div → span):直接标记“替换”,终止对该节点子树的 DFS,跳到下一个兄弟节点
- key + tag 相同:继续深入,递归 patch 其子节点(即开启下一层 DFS)
- key 不同但 tag 相同(如两个 div,key 分别为 "a" 和 "b"):视为不同节点,触发移动/插入逻辑(常借助 list-diff2 等算法重排子节点),而非逐个递归比对
Patch 过程的执行顺序 = DFS 访问顺序 + 后续批量提交
以一个简单结构为例:<div>
<p key="1">A</p>
<span key="2">B</span>
</div>
- 第一步:patch 根 div → 比对 tag/key → 相同 → 进入子节点比对
- 第二步:patch 第一个子节点 p(key="1")→ 比对成功 → patch 其文本子节点 "A"
- 第三步:回溯,patch 第二个子节点 span(key="2")→ 比对成功 → patch 其文本子节点 "B"
- 所有节点访问完毕后,按上述顺序累积的 patch 操作(如更新 textContent、设置 class)被依次应用到真实 DOM
为什么不用广度优先(BFS)?
BFS 虽然利于并行或中断恢复,但在虚拟 DOM 场景中存在明显短板:
- 需额外维护队列,内存开销略高;而 DFS 借助调用栈,天然轻量
- 父子依赖强:子节点是否需要 patch,取决于父节点是否复用;BFS 可能先处理深层节点,却尚未确认其父节点状态
- 实际渲染具有局部性,用户交互常集中于某块区域;DFS 能更快收敛到变更热点,利于 early-exit 优化










