Q24
★ ★ ★ ★ ★
详细阐述 Vue 3 的 DOM diff 原理?
⚡ 速记答案(30 秒)
触发更新时新旧 VNode 树对比,只找差异部分。子节点 diff:先从两端向中间比,用 key 建立映射,求最长递增子序列(LIS)确定稳定节点,其余节点从后往前插入/移动。
📖 详细讲解
标准面试回答(推荐记住)
Vue 3 的 diff 算法在组件更新时对比新旧 VNode 树。对于子节点列表,先从两端向中间比较跳过相同的前缀后缀,然后用 key 建立新节点的索引映射,构建新旧索引对应关系,在其上求最长递增子序列(LIS),LIS 中的节点视为稳定不需要移动,其余节点从后往前插入或移动。
快速 Diff 算法 (Fast Diff)
步骤:
预处理 (Sync):
- 从头对比,跳过相同前缀节点。
- 从尾对比,跳过相同后缀节点。
- 如果此时旧节点遍历完,新节点有剩余 -> 挂载 (Mount) 剩余新节点。
- 如果此时新节点遍历完,旧节点有剩余 -> 卸载 (Unmount) 剩余旧节点。
未知序列处理 (Unknown Sequence):
- 如果都有剩余,进入核心 diff。
- 建立新节点的
key -> index映射表。 - 遍历旧节点,能在新表找到的保留,找不到的卸载。
- 生成一个
newIndexToOldIndexMap数组,记录新旧对应关系。 - 计算该数组的 最长递增子序列 (LIS)。
移动 (Move):
- LIS 中的节点保持不动(因为它们相对顺序没变)。
- 不在 LIS 中的节点需要移动(或新增)。
Vue 2 对比:
Vue 2 使用双端指针 (Double Ended),无法像 Vue 3 这样通过 LIS 精确计算出最小移动次数。
✅ 面试要点
- •理解双端对比的优化
- •知道 key 的重要作用
- •能解释 LIS 算法的应用
💻 代码示例
diff 过程示意
// 假设列表从 [A, B, C, D, E] 变为 [A, D, B, E, C]
// 1. 前后缀对比:A 相同跳过,末尾无相同
// 2. 剩余 [B, C, D, E] -> [D, B, E, C]
// 3. 建立新节点 key -> index 映射:
// { D: 0, B: 1, E: 2, C: 3 }
// 4. 遍历旧节点,构建 newIndexToOldIndex:
// [3, 1, 4, 2] (D在旧索引3, B在1, E在4, C在2)
// 5. 求 LIS:[1, 2] 对应 [B, C],这两个节点不动
// 6. 从后往前处理:
// - C 在 LIS 中,不动
// - E 不在 LIS,移动到 C 前
// - B 在 LIS 中,不动
// - D 不在 LIS,移动到 B 前