Vue2 Diff和Vue 3 Diff实现及底层原理

49 阅读3分钟

一、一句话总览(先建立全局认知)

维度Vue 2 DiffVue 3 Diff
核心算法双端比较(双指针)最长递增子序列(LIS)
比较单位VNodeBlock + 动态节点
更新策略全量递归对比编译期标记 + 精准更新
时间复杂度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只有文本可能变
CLASSclass 可能变
STYLEstyle 可能变
PROPSprops 可能变

👉 没 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 2Vue 3
diff 发生时机运行时编译期 + 运行时
静态节点每次都比直接跳过
列表优化双端指针LIS
移动次数不确定理论最少
key 重要性很重要更重要

五、底层原理一句话总结(高质量面试回答)

Vue 2 是“运行时驱动的启发式 diff”
Vue 3 是“编译期标记 + 最小化 DOM 操作的确定性 diff”


六、你这个问题在面试怎么说(示例)

Vue 2 主要使用双端指针 diff,通过 key 映射减少 DOM 操作,但所有节点都需要参与比较。
Vue 3 在编译期引入 PatchFlags 和 Block Tree,只对动态节点做 diff,列表更新时结合最长递增子序列,保证 DOM 移动次数最少,整体性能和稳定性都更好。