《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 方案。

浙公网安备 33010602011771号