《Vue.js设计与实现》笔记 第10章:双端 Diff 算法

Vue.js设计与实现 第10章:双端 Diff 算法

本章导读

第9章简单Diff:

遍历新节点

↓

遍历旧节点

↓

寻找相同key

复杂度:

O(n²)

例如:

old = [
 A,
 B,
 C,
 D
]

new = [
 D,
 A,
 B,
 C
]

简单Diff:

不断查找

效率较低。


因此引入:

双端Diff

一、什么是双端Diff

核心思想

同时从:

旧节点两端

+

新节点两端

开始比较。


维护四个索引:

oldStartIdx
oldEndIdx

newStartIdx
newEndIdx

对应:

oldStart

↓

A B C D

        ↑

      oldEnd

newStart

↓

D A B C

        ↑

      newEnd

二、四个比较方向

双端Diff每轮比较:


① 旧头 vs 新头

oldStartVNode
newStartVNode

② 旧尾 vs 新尾

oldEndVNode
newEndVNode

③ 旧头 vs 新尾

oldStartVNode
newEndVNode

④ 旧尾 vs 新头

oldEndVNode
newStartVNode

三、为什么是四种比较

因为列表变化最常见情况:

头部新增

尾部新增

节点移动到头

节点移动到尾

通过四种比较:

大部分情况无需遍历

即可找到目标节点。


四、头头比较

示例

旧:

A B C

新:

A B D

比较:

A === A

成立:

patch(A,A)

然后:

oldStart++
newStart++

继续比较。


五、尾尾比较

示例

旧:

A B C

新:

D B C

比较:

C === C

成立:

patch(C,C)

然后:

oldEnd--
newEnd--

继续。


六、头尾比较

示例

旧:

A B C D

新:

B C D A

比较:

旧头 A

新尾 A

发现:

同节点

说明:

A从头移动到尾

处理:

复用DOM

移动位置

七、尾头比较

示例

旧:

A B C D

新:

D A B C

比较:

旧尾 D

新头 D

成立。


说明:

D从尾移动到头

处理:

移动DOM

八、DOM移动原理

旧尾移动到头

旧:

A B C D

新:

D A B C

操作:

insert(
 D.el,
 container,
 A.el
)

结果:

D A B C

九、四种情况都失败

示例

旧:

A B C D

新:

A E B C D

比较:

四种都不匹配

怎么办?


十、暴力查找

从旧节点中寻找:

newStartVNode.key

例如:

E

查找:

oldChildren.find()

结果:

找不到

说明:

新增节点

十一、新增节点

示例

旧:

A B C

新:

A E B C

E不存在。


执行:

patch(
 null,
 E
)

然后:

insert(
 E.el,
 container,
 oldStart.el
)

插入正确位置。


十二、找到旧节点

示例

旧:

A B C D

新:

A D B C

查找:

D存在

旧索引:

3

处理:

复用DOM

然后:

patch()

移动:

insert(
 D.el,
 container,
 A.el.nextSibling
)

十三、标记已处理节点

移动后:

oldChildren[idx]=undefined

作用:

避免重复匹配

例如:

[
 A,
 B,
 undefined,
 D
]

十四、循环结束条件

条件:

while(
 oldStartIdx <= oldEndIdx
 &&
 newStartIdx <= newEndIdx
)

表示:

两边都还有节点

继续比较。


十五、新节点剩余

示例

旧:

A B

新:

A B C D

循环结束:

旧节点已处理完

剩余:

C D

直接挂载。


十六、旧节点剩余

示例

旧:

A B C D

新:

A B

剩余:

C D

执行:

unmount()

删除。


十七、时间复杂度

理想情况

头头:

O(n)

尾尾:

O(n)

头尾:

O(n)

尾头:

O(n)

整体:

接近O(n)

十八、双端Diff缺点

特殊情况

例如:

A B C D E F G

↓

G F E D C B A

四种比较频繁失败。


进入:

大量查找

性能下降。


因此Vue3继续优化。


十九、为什么Vue3放弃双端Diff

Vue2:

双端Diff

Vue3:

快速Diff

原因:

快速Diff更快

DOM移动更少

引入:

最长递增子序列
LIS

二十、核心执行流程

旧头 vs 新头

↓

旧尾 vs 新尾

↓

旧头 vs 新尾

↓

旧尾 vs 新头

↓

查找

↓

新增

↓

删除

第10章核心知识图谱

Double Ended Diff

oldStart
oldEnd

newStart
newEnd

       │

       ▼

四次比较

 ┌─────────────┐

 │ 头头比较     │

 │ 尾尾比较     │

 │ 头尾比较     │

 │ 尾头比较     │

 └─────────────┘

       │

       ▼

patch()

       │

 ┌─────┼─────┐

 ▼     ▼     ▼

复用   移动   新增

       │

       ▼

删除剩余节点

高频面试题

什么是双端Diff?

同时从:

旧节点头尾

新节点头尾

开始比较。


为什么叫双端Diff?

因为:

从两端向中间收缩

进行比较。


双端Diff维护几个指针?

四个:

oldStartIdx

oldEndIdx

newStartIdx

newEndIdx

双端Diff有哪四种比较?

头头

尾尾

头尾

尾头

旧尾与新头匹配说明什么?

节点从尾移动到头

为什么需要标记undefined?

避免:

节点重复匹配

双端Diff时间复杂度?

理想情况:

O(n)

为什么Vue3不用双端Diff?

因为:

快速Diff

+

LIS

性能更好

本章总结

双端Diff核心思想:

同时比较:

旧头
旧尾

新头
新尾

通过:

头头

尾尾

头尾

尾头

快速发现:

复用节点

移动节点

新增节点

删除节点

相比简单Diff:

O(n²)

优化到:

接近O(n)

这是 Vue2 最终采用的 Diff 算法。

但仍存在局限。

因此 Vue3 在下一章进一步升级为:

快速Diff

+

最长递增子序列(LIS)

形成目前 Vue3 使用的最终 Diff 方案。

posted @ 2025-05-26 14:52  Li_pk  阅读(4)  评论(0)    收藏  举报