Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

    • ref
    • reactive
    • 基础对象的代理
    • 数组代理的方法
    • 集合对象的代理
    • Reflect和Proxy详解
    • 依赖的收集与触发
      • 概览
      • 源码分析
        • Dep类
        • 辅助类或方法
        • Link类
        • addSub
    • effectScope解析
    • effect解析
    • reactive响应式依赖的收集与触发监听
    • 批量更新实现
    • ReactiveEffect类介绍
    • computed
  • 5.18源码学习》
  • reactivity响应式
东流
2025-08-20
目录

依赖的收集与触发

# 概览

本文主要讲述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

当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

# 辅助类或方法

# 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
  • 核心逻辑:
    1. 副作用函数执行前:所有旧链接的version设置为-1
    2. 访问依赖时,同步更新version的值为Dep的version
    3. 副作用执行完成后,清理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
编辑 (opens new window)
上次更新: 2025/08/26, 10:34:53
Reflect和Proxy详解
effectScope解析

← Reflect和Proxy详解 effectScope解析→

最近更新
01
computed
08-26
02
ReactiveEffect类介绍
08-26
03
批量更新实现
08-25
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式