《Vue.js设计与实现》笔记 第9章:简单 Diff 算法
Vue.js设计与实现 第9章:简单 Diff 算法
本章导读
前面第8章已经实现:
Element节点
创建
更新
删除
但是:
oldChildren = [
A,
B,
C
]
newChildren = [
B,
C,
D
]
这种情况无法高效处理。
如果直接:
删除全部旧节点
重新创建全部新节点
虽然正确:
性能极差
因此需要:
Diff算法
目的:
尽可能复用已有DOM
一、什么是Diff算法
定义
Diff:
Difference
差异比较
作用:
比较两棵VNode树
找出变化部分
最小化DOM操作
示例
旧节点:
[
A,
B,
C
]
新节点:
[
A,
B,
D
]
理想结果:
复用A
复用B
删除C
新增D
而不是:
全部删除
全部重建
二、为什么需要Key
没有Key
旧:
<p>A</p>
<p>B</p>
<p>C</p>
新:
<p>B</p>
<p>C</p>
<p>D</p>
Vue只能:
按索引比较
结果:
A → B
B → C
C → D
全部更新。
有Key
旧:
[
{key:1},
{key:2},
{key:3}
]
新:
[
{key:2},
{key:3},
{key:4}
]
可以准确知道:
1 删除
2 复用
3 复用
4 新增
三、Key的作用
Key本质:
VNode唯一标识
Diff时:
oldVNode.key
===
newVNode.key
表示:
同一个节点
因此:
可以复用DOM
四、简单Diff核心思想
遍历新节点
for(
const newVNode
of newChildren
)
在旧节点中查找
for(
const oldVNode
of oldChildren
)
判断:
oldVNode.key
===
newVNode.key
找到:
复用节点
找不到:
新增节点
五、DOM复用
示例
旧:
[
{key:1},
{key:2},
{key:3}
]
新:
[
{key:2},
{key:3}
]
流程:
找到2
patch更新
找到3
patch更新
实现:
patch(
oldVNode,
newVNode
)
复用:
newVNode.el =
oldVNode.el
六、判断节点移动
示例
旧:
[
1,
2,
3
]
新:
[
3,
1,
2
]
节点:
全部存在
但是:
位置改变
需要:
移动DOM
七、寻找是否需要移动
引入lastIndex
记录:
已处理节点中
最大的旧索引
初始化:
let lastIndex = 0
八、移动判断原理
示例
旧:
A B C D
0 1 2 3
新:
D A B C
处理:
D
旧索引=3
lastIndex=3
继续:
A
旧索引=0
发现:
0 < 3
说明:
A发生移动
九、移动规则
如果:
oldIndex
<
lastIndex
说明:
当前节点需要移动
否则:
lastIndex = oldIndex
表示:
当前位置正常
十、如何移动DOM
找锚点
例如:
insert(
el,
container,
anchor
)
anchor:
插入位置参考节点
效果:
把DOM移动到正确位置
十一、新增节点处理
示例
旧:
[
1,
2
]
新:
[
1,
2,
3
]
查找:
3不存在
处理:
创建新节点
执行:
patch(
null,
newVNode
)
十二、确定新增位置
示例
新:
[
1,
2,
3
]
新增:
3
锚点:
下一个节点
如果:
没有下一个节点
则:
appendChild
十三、删除节点处理
示例
旧:
[
1,
2,
3
]
新:
[
1,
2
]
遍历旧节点:
oldChildren
发现:
3不存在
执行:
unmount()
删除DOM。
十四、完整Diff流程
第一步
遍历:
newChildren
第二步
寻找:
key相同节点
找到:
patch更新
找不到:
新增
第三步
判断:
是否移动
第四步
遍历:
oldChildren
不存在于:
newChildren
执行:
删除
十五、简单Diff时间复杂度
外层循环
newChildren
内层循环
oldChildren
复杂度:
O(n²)
为什么慢
例如:
1000节点
1000 × 1000
=
100万次比较
性能较差。
十六、简单Diff优缺点
优点
实现简单。
容易理解。
缺点
时间复杂度:
O(n²)
大量节点:
性能差
因此:
Vue2最终采用:
双端Diff
Vue3最终采用:
快速Diff
十七、Vue中的节点类型
Diff过程中比较:
type
以及:
key
同时满足:
type相同
key相同
才能:
复用DOM
否则:
卸载
重新创建
十八、为什么Key不能使用Index
错误示例
<div
v-for="item in list"
:key="index"
/>
插入头部:
A B C
↓
D A B C
结果:
全部错位
大量DOM更新。
正确方式
:key="item.id"
保证:
稳定且唯一
第9章核心知识图谱
Simple Diff
oldChildren
│
▼
newChildren
│
▼
寻找相同key
│
┌─────┼─────┐
▼ ▼ ▼
复用 新增 删除
│
▼
移动判断
│
▼
oldIndex
↓
lastIndex
↓
DOM Move
高频面试题
Diff算法作用是什么?
比较:
新旧VNode
找出:
- 新增
- 删除
- 更新
- 移动
为什么需要Key?
唯一标识VNode。
帮助DOM复用。
Key和Type都要比较吗?
需要。
满足:
type相同
key相同
才能复用。
简单Diff时间复杂度是多少?
O(n²)
lastIndex作用是什么?
判断:
节点是否需要移动
为什么不推荐Index作为Key?
列表变化时:
节点错位
大量更新
新增节点如何处理?
patch(
null,
vnode
)
删除节点如何处理?
unmount(vnode)
本章总结
简单Diff算法核心思想:
遍历新节点
↓
寻找可复用节点
↓
更新
↓
移动
↓
新增
↓
删除
判断节点复用:
type相同
+
key相同
判断移动:
oldIndex < lastIndex
特点:
实现简单
但复杂度O(n²)
因此后续:
第10章
双端Diff
↓
第11章
快速Diff
将进一步优化性能。
简单Diff是理解所有Diff算法的基础,也是面试中经常考察的核心知识点。

浙公网安备 33010602011771号