一、一句话总览(先建立全局认知)
| 维度 | Vue 2 Diff | Vue 3 Diff |
|---|---|---|
| 核心算法 | 双端比较(双指针) | 最长递增子序列(LIS) |
| 比较单位 | VNode | Block + 动态节点 |
| 更新策略 | 全量递归对比 | 编译期标记 + 精准更新 |
| 时间复杂度 | O(n)(最坏仍有重复操作) | O(n log n) |
| 性能瓶颈 | 无法判断"哪些节点一定不变" | 只 diff 动态节点 |
| 核心思想 | 运行时尽力猜 | 编译期提前告诉运行时 |
二、Vue 2 Diff:双端比较算法(运行时驱动)
1️⃣ Vue 2 的 diff 入口
patch(oldVnode, vnode)
如果是同一节点:
sameVnode(oldVnode, vnode)
判断条件包括:
-
key
-
tag
-
isComment
-
data 是否存在
2️⃣ 核心算法:双端指针(4 种命中)
oldStart → ← oldEnd
newStart → ← newEnd
Vue 2 会依次尝试这 4 种匹配:
1️⃣ oldStart vs newStart
2️⃣ oldEnd vs newEnd
3️⃣ oldStart vs newEnd
4️⃣ oldEnd vs newStart
命中就:
-
patch
-
移动指针
-
移动 DOM
3️⃣ Vue 2 Diff 示例(key 很重要)
<ul>
<li key="a">A</li>
<li key="b">B</li>
<li key="c">C</li>
</ul>
更新为:
<ul>
<li key="b">B</li>
<li key="a">A</li>
<li key="c">C</li>
</ul>
Vue 2 做了什么?
-
通过 key map 找到旧节点
-
移动 DOM
-
patch 内容
👉 能用,但全靠运行时判断
4️⃣ Vue 2 的问题在哪里?
❌ 无法提前知道哪些节点不会变
❌ 每一层都要递归 diff
❌ 静态节点也要参与比较
❌ 复杂列表性能不稳定
Vue 2 的哲学:
"我运行时尽量少改 DOM,但我不知道哪些一定不会变"
三、Vue 3 Diff:Block Tree + LIS(编译期 + 运行时)
这是 Vue 3 真正"质变"的地方。
1️⃣ 核心思想变化(非常重要)
Vue 3 把 diff 的一半工作,提前到"编译期"做完了
2️⃣ 编译期:PatchFlags(关键)
Vue 3 编译模板时:
<div>
<p>{{ msg }}</p>
<span>static</span>
</div>
会生成类似:
createVNode("p", null, msg, PatchFlags.TEXT)
PatchFlags 告诉运行时:
| Flag | 含义 |
|---|---|
| TEXT | 只有文本可能变 |
| CLASS | class 可能变 |
| STYLE | style 可能变 |
| PROPS | props 可能变 |
👉 没 flag 的节点,直接跳过 diff
3️⃣ Block Tree(块级优化)
Vue 3 会把模板拆成 Block:
Block
├── 动态节点 1
├── 动态节点 2
⚠️ Block 里只存动态节点
4️⃣ 运行时 Diff:最长递增子序列(LIS)
当列表乱序时:
[a, b, c, d] → [b, a, d, c]
Vue 3 会:
1️⃣ 通过 key 建立映射
2️⃣ 得到旧节点索引数组
[1, 0, 3, 2]
3️⃣ 对这个数组求 最长递增子序列
LIS = [1, 3]
👉 LIS 里的节点不用动
👉 其他节点才移动 DOM
这一步是 Vue 3 真正快的原因
5️⃣ Vue 3 Diff 过程总结
编译期:
模板 → PatchFlags → Block Tree
运行时:
只 diff 动态节点
列表用 LIS 算最少 DOM 移动
四、Vue 2 vs Vue 3 Diff 对比(面试必背)
| 点 | Vue 2 | Vue 3 |
|---|---|---|
| diff 发生时机 | 运行时 | 编译期 + 运行时 |
| 静态节点 | 每次都比 | 直接跳过 |
| 列表优化 | 双端指针 | LIS |
| 移动次数 | 不确定 | 理论最少 |
| key 重要性 | 很重要 | 更重要 |
五、底层原理一句话总结(高质量面试回答)
Vue 2 是"运行时驱动的启发式 diff"
Vue 3 是"编译期标记 + 最小化 DOM 操作的确定性 diff"
六、你这个问题在面试怎么说(示例)
Vue 2 主要使用双端指针 diff,通过 key 映射减少 DOM 操作,但所有节点都需要参与比较。
Vue 3 在编译期引入 PatchFlags 和 Block Tree,只对动态节点做 diff,列表更新时结合最长递增子序列,保证 DOM 移动次数最少,整体性能和稳定性都更好。