上文,我们分析了渲染过程中响应式机制的建立。那么接下来我们就来具体分析响应式机制的具体实现细节,本文先分析依赖收集的具体实现细节。
一、整体设计
Vue 的依赖收集采用了精心设计的数据结构和算法,主要包括以下几个核心部分:
- Link 类:双向链表节点,连接依赖(Dep)和订阅者(Subscriber)
 - Dep 类:管理依赖关系的核心类
 - ReactiveEffect 类:响应式效果的实现,作为订阅者
 - 依赖收集的数据结构:采用 WeakMap + Map + Set 的组合
 
二、核心数据结构
1. Link 类 - 依赖链接
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
export class Link {
  // 版本号,用于依赖清理
  version: number;
  // 双向链表指针
  nextDep?: Link; // 指向下一个依赖
  prevDep?: Link; // 指向前一个依赖
  nextSub?: Link; // 指向下一个订阅者
  prevSub?: Link; // 指向前一个订阅者
  prevActiveLink?: Link; // 指向前一个活动链接
  constructor(
    public sub: Subscriber, // 订阅者(effect)
    public dep: Dep // 依赖项
  ) {
    this.version = dep.version;
  }
}
        Link 类的设计亮点:
- 使用双向链表实现依赖和订阅者的多对多关系
 - 通过版本号机制优化依赖清理
 - 支持快速遍历和更新依赖关系
 
2. Dep 类 - 依赖管理
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
export class Dep {
  version = 0; // 依赖版本号
  activeLink?: Link = undefined; // 当前活动的依赖链接
  subs?: Link = undefined; // 订阅者链表尾部
  subsHead?: Link; // 订阅者链表头部(仅开发环境)
  // 用于对象属性依赖清理
  map?: KeyToDepMap = undefined;
  key?: unknown = undefined;
  // 订阅者计数
  sc: number = 0;
  constructor(public computed?: ComputedRefImpl | undefined) {
    if (__DEV__) {
      this.subsHead = undefined;
    }
  }
  // 依赖收集
  track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
    if (!shouldTrack || !activeSub) {
      return undefined;
    }
    // 创建新的依赖链接
    const link = new Link(activeSub, this);
    // 将链接添加到依赖和订阅者的链表中
    addSub(link);
    return link;
  }
  // 触发更新
  trigger(debugInfo?: DebuggerEventExtraInfo): void {
    if (this.subs) {
      startBatch();
      this.notify(debugInfo);
      endBatch();
    }
  }
}
        Dep 类的核心功能:
- 管理订阅者链表
 - 提供依赖收集接口(track)
 - 提供更新触发接口(trigger)
 - 支持版本控制和计数统计
 
3. 依赖收集的全局数据结构
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
// 类型定义
type KeyToDepMap = Map<any, Dep>;
const targetMap = new WeakMap<object, KeyToDepMap>();
// 依赖收集过程
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  if (!shouldTrack || !activeSub) {
    return;
  }
  // 获取目标对象的依赖 Map
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  // 获取属性的依赖集合
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Dep()));
  }
  // 收集依赖
  dep.track();
}
        数据结构设计的优点:
- 使用 WeakMap 避免内存泄漏
 - Map 实现键值对的快速查找
 - 支持任意类型的 key
 - 层次化的结构便于管理和清理
 
4. 双向链表结构说明
            
            
              js
              
              
            
          
          export class Link {
  // 双向链表指针
  nextDep?: Link; // 指向下一个依赖
  prevDep?: Link; // 指向前一个依赖
  nextSub?: Link; // 指向下一个订阅者
  prevSub?: Link; // 指向前一个订阅者
  prevActiveLink?: Link; // 指向前一个活动链接
  constructor(
    public sub: Subscriber, // 订阅者(effect)
    public dep: Dep // 依赖项
  ) {
    this.version = dep.version;
  }
}
        Link节点把所有的发布者Dep和订阅者Sub链接成一张网,便于从deps何subs两个维度遍历数据。
这种双向链表结构的优势:
- 
高效的依赖追踪:
- effect 可以通过 deps 快速找到所有依赖
 - dep 可以通过 subs 快速找到所有订阅者
 
 - 
灵活的节点操作:
- 支持节点的快速插入和删除
 - O(1) 时间复杂度的头尾操作
 - 方便进行节点的移动和重排
 
 - 
优化的内存使用:
- 共享 Link 节点减少内存占用
 - 无需额外的数组或集合
 - 支持节点的复用
 
 - 
清晰的依赖关系:
- 双向链表清晰展示依赖关系
 - 便于调试和依赖追踪
 - 支持正向和反向遍历
 
 
三、依赖收集过程
1. 收集触发点
依赖收集发生在访问响应式数据时:
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/baseHandlers.ts
class BaseReactiveHandler {
  get(target: object, key: unknown, receiver: object) {
    // ... 其他逻辑 ...
    // 追踪依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key);
    }
    return result;
  }
}
        2. 收集流程
以下面的组件为例,分析依赖收集的完整执行过程:
            
            
              js
              
              
            
          
          <template>
  <div>{{ title }}</div>
</template>
<script>
export default {
  props: {
    title: String,
  },
};
</script>
        当组件渲染时,会执行以下流程:
- 渲染函数访问数据:
 
            
            
              js
              
              
            
          
          // 渲染函数中访问 title 属性
_ctx.title; // 这里的 _ctx 是组件实例的代理对象
        - 触发代理的 get 函数:
 
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/baseHandlers.ts
class BaseReactiveHandler {
  get(target: object, key: unknown, receiver: object) {
    // 获取原始值
    const res = Reflect.get(target, key, receiver);
    // 追踪依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key);
    }
    return res;
  }
}
        - 执行全局 track 函数:
 
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  // 检查是否应该收集依赖
  if (shouldTrack && activeSub) {
    // 获取目标对象的依赖 Map
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      // 如果不存在,创建一个新的 Map
      targetMap.set(target, (depsMap = new Map()));
    }
    // 获取属性的依赖对象
    let dep = depsMap.get(key);
    if (!dep) {
      // 如果不存在,创建一个新的 Dep 实例
      depsMap.set(key, (dep = new Dep()));
      // 保存反向引用,用于清理
      dep.map = depsMap;
      dep.key = key;
    }
    // 开发环境下收集更多的调试信息
    if (__DEV__) {
      dep.track({
        target,
        type,
        key,
      });
    } else {
      dep.track();
    }
  }
}
        - 执行 Dep 实例的 track 方法:
 
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
export class Dep {
  track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
    // 检查是否应该收集依赖
    if (!activeSub || !shouldTrack || activeSub === this.computed) {
      return;
    }
    // 尝试复用或创建新的 Link
    let link = this.activeLink;
    if (link === undefined || link.sub !== activeSub) {
      // 创建新的 Link 并建立链表关系
      link = this.activeLink = new Link(activeSub, this);
      // 将 link 添加到 activeSub 的依赖链表尾部
      if (!activeSub.deps) {
        activeSub.deps = activeSub.depsTail = link;
      } else {
        link.prevDep = activeSub.depsTail;
        activeSub.depsTail!.nextDep = link;
        activeSub.depsTail = link;
      }
      addSub(link);
    } else if (link.version === -1) {
      // 复用上次运行的 link - 已经是订阅者,只需同步版本
      link.version = this.version;
      // 如果这个 dep 有下一个节点,说明它不在尾部
      // 需要将它移动到尾部,确保 effect 的依赖列表按访问顺序排序
      if (link.nextDep) {
        // 1. 从当前位置断开
        const next = link.nextDep;
        next.prevDep = link.prevDep;
        if (link.prevDep) {
          link.prevDep.nextDep = next;
        }
        // 2. 移动到尾部
        link.prevDep = activeSub.depsTail;
        link.nextDep = undefined;
        activeSub.depsTail!.nextDep = link;
        activeSub.depsTail = link;
        // 如果是头节点,更新新的头节点
        if (activeSub.deps === link) {
          activeSub.deps = next;
        }
      }
    }
    // 开发环境的依赖追踪
    if (__DEV__ && activeSub.onTrack) {
      activeSub.onTrack(
        extend(
          {
            effect: activeSub,
          },
          debugInfo
        )
      );
    }
    return link;
  }
}
        - 建立依赖关系:
 
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/dep.ts
function addSub(link: Link): void {
  const { sub, dep } = link;
  // 1. 添加到订阅者的依赖链表
  if (!sub.deps) {
    // 如果是第一个依赖,设置头尾指针
    sub.deps = sub.depsTail = link;
  } else {
    // 否则添加到链表尾部
    link.prevDep = sub.depsTail;
    sub.depsTail!.nextDep = link;
    sub.depsTail = link;
  }
  // 2. 添加到依赖的订阅者链表
  if (!dep.subs) {
    // 如果是第一个订阅者
    dep.subs = link;
    if (__DEV__) {
      dep.subsHead = link;
    }
  } else {
    // 否则添加到链表头部
    link.prevSub = dep.subs;
    dep.subs.nextSub = link;
    dep.subs = link;
  }
  // 3. 更新订阅者计数
  dep.sc++;
}
        这样,当渲染函数执行完成后:
title属性已经建立了与当前渲染 effect 的依赖关系- 这个关系被存储在全局的 
targetMap中 - 通过双向链表可以快速找到互相的引用
 - 当 
title发生变化时,就能通过这个依赖关系触发更新 
数据结构示意图:
这种设计的优点:
- 层次化的数据结构使依赖关系清晰
 - 双向链表支持快速操作和遍历
 - WeakMap 的使用避免内存泄漏
 - 完整的开发环境调试支持
 
3. 性能优化
- 版本控制:
 
            
            
              js
              
              
            
          
          // 每次响应式变化时递增
export let globalVersion = 0;
// Link 类中记录版本
this.version = dep.version;
        - 批量处理:
 
            
            
              js
              
              
            
          
          export function batch(sub: Subscriber, isComputed = false): void {
  startBatch();
  try {
    sub.notify();
  } finally {
    endBatch();
  }
}
        - 
懒清理机制:
- 在 effect 运行前重置所有依赖的版本号
 - 运行时同步依赖版本
 - 运行后清理版本号为 -1 的依赖
 
 
四、依赖清理机制
1. 清理时机
            
            
              js
              
              
            
          
          // 源码位置: packages/reactivity/src/effect.ts
function cleanupEffect(e: ReactiveEffect) {
  // 清理旧的依赖关系
  let link = e.deps;
  while (link) {
    const next = link.nextDep;
    removeDep(link);
    link = next;
  }
  e.deps = e.depsTail = undefined;
}
        2. 清理策略
- 硬清理:完全移除依赖关系
 - 软清理:保留结构但标记为无效
 - 延迟清理:等待下次收集时再清理
 
五、总结
Vue 的依赖收集机制通过精心设计的数据结构和算法实现了:
- 
高效的依赖管理:
- 双向链表实现快速操作
 - 版本控制优化清理
 - 分层结构便于管理
 
 - 
精确的更新触发:
- 准确找到相关依赖
 - 批量处理提高性能
 - 避免重复通知
 
 - 
完善的内存管理:
- 
WeakMap 避免内存泄漏
 - 
及时清理无效依赖
 - 
支持垃圾回收
 
 -