图解 Vue 3 运行时核心:快速 Diff 五步法与最长递增子序列 (LIS)
本交互式实验室与《Vue3架构分析(三)—— 渲染器与App设计》《Vue3架构分析(四)—— 组件实例的创建和初始化》紧密联动。在浏览器中操作真实 DOM 的移动(insertBefore)远比纯 JS 对象的计算昂贵,因此 Vue 3 在更新带 key 的 v-for 列表时采用了五阶段算法:
- 阶段一(头部同步): 指针
i从0开始向右扫描,只要新旧节点key相同便原地patch。 - 阶段二(尾部同步): 指针
e1(旧尾)与e2(新尾)同时向左扫描,跳过尾部相同节点。 - 阶段三 & 四(纯新增或纯卸载): 若
i > e1说明只需挂载新增节点;若i > e2说明只需卸载多余旧节点。 - 阶段五(乱序区间 + 最长递增子序列 LIS): 利用
getSequence(newIndexToOldIndexMap)找出相对顺序不变的最长子序列作为不动锚点,倒序遍历仅对非 LIS 节点调用hostInsert,实现数学意义上的最少 DOM 移动。
常见问题与实战解答 (FAQ)
Q1 Vue 3 的快速 Diff 算法相比 Vue 2 的双端 Diff 有什么核心改进?
newIndexToOldIndexMap 并使用贪心+二分查找求出【最长递增子序列 (LIS)】,凡是命中 LIS 的节点均保持原地不动,从而保证在 O(n log n) 时间内实现最小次数的真实 DOM 移动操作。 Q2 Vue 3 响应式系统是如何组织 targetMap、depsMap 和 Dep 的?
targetMap 是一个 WeakMap(键为响应式原始对象 target,值为 depsMap,当 target 无引用时可被 GC 自动回收)。 depsMap 是一个 Map(键为对象的属性名 key,值为 Dep 集合)。 ReactiveEffect 实例的集合。 track() 收集依赖,修改属性触发 trigger() 派发更新。