依赖的收集与触发
# 概览
本文主要讲述vue3中的Dep
,Dep
的全称为Dependency
,即依赖的意思。在vue3中,Dep
的主要作用是收集依赖和触发依赖。具体参考packages\reactivity\src\dep.ts
# 源码分析
在解析Dep
之前,先回顾下ref
响应的实现,伪代码如下:
class RefImpl{
constructor(value){
this._value = value;
this.dep= new Dep();
}
get value(){
this.dep.track();
return this._value;
}
set value(){
if (hasChanged(newValue, oldValue)) {
{
this.dep.trigger();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
当ref
对象的value
属性被访问时,会触发get
方法,在get
方法中会调用dep.track
方法,将当前的ref
对象添加到dep
的依赖列表中;当ref
对象的value
属性被赋值时,会触发set
方法,若新值与旧值不同,则会调用dep.trigger
方法,触发dep
的依赖列表中的所有依赖;若新值与旧值相同,则不会触发依赖。
所以Dep
与响应式是密切相关的。
# Dep
类
Dep
类负责依赖收集和更新通知,是连接响应式数据与依赖它的副作用函数(如组件渲染、计算属性)的桥梁。
Dep
类的源码实现如下:
let globalVersion = 0;
let activeSub;
let shouldTrack = true;
class Dep {
constructor(computed) {
this.computed = computed; // 关联的计算属性(若当前Dep属于计算属性)
this.version = 0; // 版本号,用于追踪数据更新次数,避免无效更新
this.activeLink = undefined; // 当前活跃的依赖链接(Link实例)
this.subs = undefined; // 订阅者链表的尾部(用于快速添加新订阅者)
this.subsHead = undefined; // 订阅者链表的头部(用于遍历所有订阅者)
this.map = undefined; // 用于存储键值与依赖的映射
this.key = undefined; // 关联的响应式数据的键名
this.sc = 0;// 状态计数器,可能用于标记依赖的活跃状态
this["__v_skip"] = true; // 内部标记,表示该对象不需要被响应式系统递归处理
}
track() {
// 过滤无效场景:无活跃订阅者、不应该追踪、订阅者是自身关联的计算属性
if (!activeSub || !shouldTrack || activeSub === this.computed) {
return;
}
let link = this.activeLink; // 获取当前活跃的依赖链接
if (link === void 0 || link.sub !== activeSub) {
// 无活跃链接或者链接的订阅者不是当前活跃者,则创建新链接
link = this.activeLink = new Link(activeSub, this);
// 将新链接添加到订阅者的依赖链表(维护订阅者的所有依赖)
if (!activeSub.deps) {
// 初始化链表
activeSub.deps = activeSub.depsTail = link;
} else {
// 双向链表关联
link.prevDep = activeSub.depsTail;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link; //更新尾部
}
addSub(link) // 将链接添加到当前Dep的订阅者链表中
} else if (link.version === -1) {
// 链接已存在但版本为 -1(可能是之前被标记为失效) --> 恢复并调整位置
link.version = this.version; // 更新版本为当前Dep的版本
if (link.nextDep) {
// 调整链表结构,将当前链接移到订阅者依赖链表的尾部(优化更新顺序)
const next = link.nextDep;
next.prevDep = link.prevDep;
if (link.prevDep) {
link.prevDep.nextDep = next;
}
link.prevDep = activeSub.depsTail;
link.nextDep = void 0;
activeSub.depsTail.nextDep = link;
activeSub.depsTail = link;
// 若当前链表是链表头部,更新头部为下一个节点
if (activeSub.deps === link) {
activeSub.deps = next;
}
}
}
return link;
}
// 触发更新
trigger(debugInfo) {
this.version++; // 递增当前Dep的版本(标记数据已更新)
globalVersion++; // 递增全局版本(用于跨dep的更新协调)
this.notify(debugInfo);//调用notify 通知订阅者
}
// 通知订阅者
notify(debugInfo) {
startBatch(); // 开始批量更新(优化性能,避免频繁更新)
try {
// 遍历当前Dep的订阅者链表(从尾部到头部)
for (let link = this.subs; link; link = link.prevSub) {
// 调用订阅者的notify方法,若返回true,则递归通知订阅者的依赖
if (link.sub.notify()) {
link.sub.dep.notify();
}
}
} finally {
endBatch();// 结束批量更新,执行所有累积的更新
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# 辅助类或方法
# Link
类
Link
类用于建立响应式依赖(Dep
)与副作用订阅者(Subscriber
)之间的双向关联,主要解决动态依赖追踪和高效清理问题。
Link
类的源码实现如下:
class Link{
constructor(sub,dep){
this.sub = sub; // 订阅者
this.dep = dep; // 依赖集合
this.version = dep.version; //版本标记
this.nextDep = undefined; // 下一个依赖
this.prevDep = undefined; // 上一个依赖
this.nextSub = undefined; // 下一个订阅者
this.prevSub = undefined; // 上一个订阅者
this.prevActiveLink = undefined; // 上一个活动链接
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 核心逻辑:
- 副作用函数执行前:所有旧链接的
version
设置为-1
- 访问依赖时,同步更新
version
的值为Dep
的version
- 副作用执行完成后,清理
version
仍为-1
的链接;因为-1
表示该依赖在本次副作用函数执行中未被引用。
- 副作用函数执行前:所有旧链接的
# addSub
addSub
函数是Dep
类依赖管理的辅助函数,用于将订阅者链接正式添加到Dep
的订阅者链表中,同时处理计算属性相关的特殊逻辑,是连接Dep
与订阅者(如Watcher
)的关键环节。
function addSub(link) {
link.dep.sc++; // 递增依赖的订阅计数器
if (link.sub.flags & 4) {//判断订阅者是否需要被添加到链表中
const computed = link.dep.computed;
if (computed && !link.dep.subs) { // 当链接的依赖存在关联的计算属性且当前dep的订阅者链表为空,即首次添加订阅者时,执行如下代码
computed.flags |= 4 | 16; // 更新计算属性的标志位
// 循环computed.deps并递归调用addSub函数
for (let l = computed.deps; l; l = l.nextDep) {
addSub(l);
}
}
// 维护订阅者链表的双向关联
const currentTail = link.dep.subs; // Dep的订阅者链表的尾部,即最后一个订阅者链接
if (currentTail !== link) { //即link不是当前尾部,则需要添加到链表
link.prevSub = currentTail; // 新link的前向指针指向当前dep订阅者的尾部,建立双向关联的前半部分
// 若currentTail存在即链表非空,则当前dep的尾部的后巷指针指向新link,完成双向关联。
if (currentTail) currentTail.nextSub = link;
}
// 更新Dep的尾部指针为新link,此时link成为链表的新尾部
link.dep.subs = link;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
编辑 (opens new window)
上次更新: 2025/08/26, 10:34:53