Vue3源码reactivity响应式篇之Dep类

发布于:2025-09-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

概览

本文主要讲述vue3中的DepDep的全称为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();
        }
      }
    }
}

ref对象的value属性被访问时,会触发get方法,在get方法中会调用dep.track方法,将当前的ref对象添加到dep的依赖列表中;当ref对象的value属性被赋值时,会触发set方法,若新值与旧值不同,则会调用dep.trigger方法,触发dep的依赖列表中的所有依赖;若新值与旧值相同,则不会触发依赖。

所以Dep与响应式是密切相关的。

Dep

Dep类负责依赖收集和更新通知,是连接响应式数据与依赖它的副作用函数(如组件渲染、计算属性)的桥梁。

Dep类的源码实现如下:

let globalVersion = 0; // 全局版本

let activeSub; // 激活的订阅者: Subscriber类型,即 ReactiveEffect 或者computed 订阅者

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();// 结束批量更新,执行所有累积的更新
        }
    }
}

辅助类或方法

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. 副作用函数执行前:所有旧链接的version设置为-1
    2. 访问依赖时,同步更新version的值为Depversion
    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;
  }
}

网站公告

今日签到

点亮在社区的每一天
去签到