📚前端面试题速记
Q24

详细阐述 Vue 3 的 DOM diff 原理?

速记答案(30 秒)

触发更新时新旧 VNode 树对比,只找差异部分。子节点 diff:先从两端向中间比,用 key 建立映射,求最长递增子序列(LIS)确定稳定节点,其余节点从后往前插入/移动。

📖 详细讲解

标准面试回答(推荐记住)

Vue 3 的 diff 算法在组件更新时对比新旧 VNode 树。对于子节点列表,先从两端向中间比较跳过相同的前缀后缀,然后用 key 建立新节点的索引映射,构建新旧索引对应关系,在其上求最长递增子序列(LIS),LIS 中的节点视为稳定不需要移动,其余节点从后往前插入或移动。

快速 Diff 算法 (Fast Diff)

步骤:

  1. 预处理 (Sync):

    • 从头对比,跳过相同前缀节点。
    • 从尾对比,跳过相同后缀节点。
    • 如果此时旧节点遍历完,新节点有剩余 -> 挂载 (Mount) 剩余新节点。
    • 如果此时新节点遍历完,旧节点有剩余 -> 卸载 (Unmount) 剩余旧节点。
  2. 未知序列处理 (Unknown Sequence):

    • 如果都有剩余,进入核心 diff。
    • 建立新节点的 key -> index 映射表。
    • 遍历旧节点,能在新表找到的保留,找不到的卸载。
    • 生成一个 newIndexToOldIndexMap 数组,记录新旧对应关系。
    • 计算该数组的 最长递增子序列 (LIS)
  3. 移动 (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 前