vue3.5 响应式系统:依赖收集与派发更新

54 阅读15分钟

Vue 3 的响应式系统基于 Proxy 进行了彻底重构,相比 Vue 2 的 Object.defineProperty 方案,它能够拦截更多操作(如属性的添加/删除、数组索引和 length 修改、Map/Set 等方法调用),并且采用 懒递归 和 精细化依赖管理,性能与可维护性都大幅提升。

副作用

概念

在 Vue 3 的响应式系统里,“副作用”(Effect)是一个核心概念。简单来说,副作用是指那些依赖于响应式数据,并且当这些数据变化时需要重新执行的函数。

  • 组件的渲染函数(当数据变化时重新生成虚拟 DOM)
  • watch 或 watchEffect 的回调(当被监听的数据变化时执行某些逻辑)
  • computed 的 getter(当其依赖变化时重新计算值)

这些函数在依赖的数据变化时会被“触发”重新运行,从而产生一些“额外的影响”(比如更新视图、发起请求、操作 DOM 等),因此被称为副作用。

副作用有哪些特点?

  1. 依赖追踪
    副作用函数在执行期间会访问响应式数据(如 refreactive 对象的属性)。Vue 的响应式系统会记录这个副作用与这些数据的对应关系。
  2. 自动重新执行
    当副作用所依赖的数据发生变化时,Vue 会自动将该副作用标记为“需要重新运行”,并在合适的时机(通常是下一个微任务)重新执行它。
  3. 可停止
    副作用可以被手动或自动停止(组件卸载时)。停止后,它不再响应任何数据变化。

一些概念

副作用实例 ReactiveEffect

在 Vue 3 的响应式系统中,ReactiveEffect 是代表一个副作用(Effect) 的核心类。任何需要“在响应式数据变化时自动重新执行”的逻辑,都会被封装成一个 ReactiveEffect 实例。

依赖容器 Dep 

在 Vue 3 的响应式系统中,Dep 是 Dependency(依赖)  的缩写,它代表一个依赖容器。每个响应式数据(例如 ref 的 .valuereactive 对象的某个属性、computed 的计算结果)都对应一个唯一的 Dep 实例。Dep 负责存储所有依赖该数据的副作用(ReactiveEffect ,并在数据变化时通知它们更新。

双向链表节点 Link

在 Vue 3 的响应式系统中,Link 是一个核心的数据结构,用于连接 ReactiveEffect(副作用)  和 Dep(依赖容器) ,形成双向链表。每个 Link 节点同时存在于 effect.deps 链表(记录该 effect 依赖的所有 Dep)和 dep.subs 链表(记录依赖该 Dep 的所有 effect)。这种设计使得依赖关系的增删和更新操作都能在 O(1)  时间内完成,并且为高效的增量依赖清理提供了基础。

Dep.version 与 Link.version 协作机制

Dep.version:每次该数据被修改(通过 set 陷阱),dep.version 会自增 1

image.png

Link.version:Effect 上一次看到的版本。

步骤1: effect 准备运行(prepareDeps

Vue 在每次执行 effect 前,会遍历该 effect 的 deps 链表,将所有 Link.version 设置为 -1

image.png

步骤2: effect 执行(run 中的 fn
  • 当 effect 内的函数访问某个响应式数据时,会触发 track
  • track 会找到或创建与该 Dep 和当前 Effect 关联的 Link 节点。
    • 如果 Link 已存在且其 version === -1(本轮尚未访问),则将其 version 更新为当前 dep.version(一个正数),并将该 Link 移动到 effect 依赖链表的尾部(LRU 优化)。
    • 如果 Link 是新创建的,version 直接设置为 dep.version
  • 这样,本次运行中被访问过的依赖,其 Link.version 被更新为 dep.version(不再是 -1)。

image.png

Vue 3 维护 effect.deps 链表时采用了 LRU(最近最少使用)  策略:

  • 链表尾部:存放本次 effect 执行中最近被访问的依赖。
  • 链表头部:存放可能已过时的依赖(即上次执行后未被再次访问的依赖)。

移动 link 到尾部的目的,是在 effect 执行结束后,能够高效地清理无效依赖

  • 清理阶段(cleanupDeps)会从链表头部开始遍历,遇到第一个 version !== -1 的节点时停止。
  • 由于被访问过的依赖都被移到了尾部,头部连续区域全是 version === -1 的废弃节点,清理时只需删除头部一段连续的节点,而无需扫描整个链表。
步骤3: effect 执行完毕(cleanupDeps
  • 再次遍历 effect 的 deps 链表,检查每个 Link.version
    • 如果 Link.version === -1 → 该依赖在本次执行中没有被访问 → 调用 removeSub(link) 将其从 Dep.subs 和 Effect.deps 中移除(即清理失效依赖)。
    • 如果 Link.version !== -1 → 依赖仍然有效,保留在链表中。
function cleanupDeps(sub: Subscriber) {
  // Cleanup unused deps
  let head // 用于记录新的依赖链表头部
  let tail = sub.depsTail // 从订阅者的 depsTail 开始,即依赖链表的尾部
  let link = tail // 当前处理的依赖链接,初始化为 tail

  // 逆序遍历依赖链表
  while (link) {
    const prev = link.prevDep
    // 在 prepareDeps 函数中,所有依赖的版本号会被重置为 -1
    if (link.version === -1) {
      if (link === tail) tail = prev
      // unused - remove it from the dep's subscribing effect list
      removeSub(link)
      // also remove it from this effect's dep list
      removeDep(link)
    } else {
      // The new head is the last node seen which wasn't removed
      // from the doubly-linked list
      head = link
    }

    // restore previous active link if any
    // 复依赖的 activeLink 为之前保存的 prevActiveLink
    link.dep.activeLink = link.prevActiveLink
    link.prevActiveLink = undefined // 清除 prevActiveLink,避免内存泄漏
    link = prev
  }
  // set the new head & tail
  sub.deps = head
  sub.depsTail = tail
}

effect API

Vue3 响应式系统的核心机制,负责创建一个响应式副作用(Reactive Effect),并立即执行一次,返回一个可以手动触发该副作用的 runner 函数

语法

// 语法
const runner = effect(fn,options:ReactiveEffectOptions)
interface ReactiveEffectOptions extends DebuggerOptions {
  // 调度器函数,用于在依赖变化时调用
  scheduler?: EffectScheduler
  // 是否允许递归调用
  allowRecurse?: boolean
  // 停止函数,用于在订阅者被移除时调用
  onStop?: () => void
}

示例 多个副作用 关联同一个响应式数据

import { effect, ref } from "vue";
const color = ref(0);
const count = ref(0);
const title = ref("云平台首页新");

const handleClick = () => {
  count.value++;
};

/*  情况一: 多个 effect 订阅 同一个响应式变量 */

// 副作用函数、监听 count变化
effect(() => {
  console.log("effect1 count 值", count.value);
});

// 副作用函数、监听 count变化
effect(() => {
  console.log("effect2 count 值", count.value);
});

执行 第一个 effect , 新增双向链表 link 结构示意图

  1. 第一步:创建 count Ref响应式数据,初始化 dep = new Dep()
  2. 第二步:执行 effect, 创建副作用 ReactiveEffect(即effect1)
  3. 第三步:执行 effect1 副作用的回调函数,count.value 触发依赖收集
  4. 第四步:执行 countRef.dep.track, 触发依赖收集,创建双向链表 Link(sub,dep)
  5. 第五步:建立 effect1link 的关联
  6. 第六步:建立 deplink 的关联

image.png

执行 第二个 effect , 新增双向链表 link 结构示意图

  1. 第一步:执行 effect, 创建副作用 ReactiveEffect(即effect2)
  2. 第二步:执行 effect2 副作用的回调函数,count.value 触发依赖收集
  3. 第三步:执行 countRef.dep.track, 触发依赖收集,创建双向链表 Link2(effect2,dep)
  4. 第四步:建立 effect2link2 的关联
  5. 第五步:建立 deplink2 的关联

image.png

点击按钮触发依赖更新,从 dep.subs 尾指针开始向前遍历。

image.png

触发 trigger 函数,从 dep.subs (订阅者链表尾指针 往前遍历),触发 link.sub.notify()

image.png

遍历双向链表指针,将 effect 添加到普通订阅者的批处理队列头

image.png

示例 一个 effect 关联多个响应式数据

const color = ref(0);
const count = ref(0);

const handleClick = () => {
  count.value++;
};
effect(() => {
  console.log("effect1 color 值", color.value);
  console.log("effect1 count 值", count.value);
});

执行过程:

  1. 创建 color Ref 对象,初始化 this.dep = new Dep()
  2. 创建 count Ref 对象,初始化 this.dep = new Dep()
  3. 创建 effect 副作用
  4. 执行副作用回调函数
    • 读取 color.value ,触发依赖收集,创建 link 双向链表,关联effectcolorRef 的依赖链表
    • 读取 count.value ,触发依赖收集,创建 link1 双向链表,关联effectcountRef 的依赖链表
    • 后创建的双向链表Link(如link1) 会在effect的依赖链表尾部。

image.png

点击按钮,触发响应式通知

image.png

示例 手动控制

effect 返回一个 runner 函数,可用于手动触发 effect 的执行

<template>
  <div>
    <h2>手动控制</h2>
    <button @click="color++">点击</button>
    <button @click="handleClick">监听</button>
    <button @click="handleStop">取消监听</button>
  </div>
</template>
<script setup lang="ts">
import { effect, ref } from "vue";
const color = ref(0);

// 返回 runner 函数,用于手动触发监听
const runner = effect(() => {
  console.log("color 值", color.value);
});

// 手动执行不受依赖变化影响
const handleClick = () => {
  runner();
};

const handleStop = () => {
  runner.effect.stop();
};
</script>

image.png

执行 runner.effect.stop()

image.png

示例 停止 effect

runner.effect.stop() 不会阻止手动调用 runner() 函数,但会影响其行为:

  1. 手动调用仍然有效:即使调用了 stop(),仍然可以通过 runner() 手动执行副作用函数
  2. 依赖追踪被禁用:手动调用时不会再追踪依赖关系,也不会在依赖变化时自动重新执行
<template>
  <div>
    <button @click="color++">点击</button>
    <button @click="handleClick">监听</button>
    <button @click="handleClick2">手动执行</button>
  </div>
</template>
<script setup lang="ts">
import { effect, ref } from "vue";
const color = ref(0);

// 返回 runner 函数,用于手动触发监听
const runner = effect(() => {
  // color 变化时,会触发监听函数
  console.log("color 值", color.value);
});

// 手动执行不受依赖变化影响
// 即使取消监听,手动执行仍会触发
const handleClick = () => {
  runner();
};

const handleClick2 = () => {
  // 停止effect监听
  runner.effect.stop();
};
</script>

示例 effect reactive对象

<template>
  <div>effect B</div>
</template>

<script lang="ts" setup>
import { effect, reactive } from "vue";

const obj = reactive({
  count: 0,
  name: "effectB",
  age: 20,
});

effect(() => {
  console.log("effectB 值", obj.count, obj.name, obj.age);
});
</script>

image.png

image.png

effect 读取 obj.count

image.png

image.png

effect 读取 obj.name

之前读取 obj.count时, target(即 reactive obj 对象) 已经缓存到 targetMap

给属性 count 设置 dep 依赖对象。

image.png

执行 dep.track

dep 是新创建的,因此需要创建 new Link(sub,dep) 双向依赖链表。

image.png

image.png

image.png

image.png

image.png

示例 effect computed

<template>
  <div>
    <p id="cdom">count 值</p>
    <button @click="handleClick">点击</button>
  </div>
</template>
<script lang="ts" setup>
import { ref, computed, effect } from "vue";

const count = ref(0);

const handleClick = () => {
  count.value++;
};
const doubleCount = computed(() => count.value * 2);

console.log(doubleCount.value);
effect(() => {
  console.log("doubleCount", doubleCount.value);
});
</script>

image.png

执行 count.value++

执行 computedEffect 的 notify(),再执行 computed.dep.notify()

继续执行 effect.notify()

image.png

示例 effect reactive数组

const reactiveArr = reactive(["a", "b", "c"]);

effect(() => {
  console.log("reactiveArr", reactiveArr[0], reactiveArr[1]);
});

image.png

示例 watch 批量更新

<template>
  <div>
    <button @click="handleClick">点击</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
const count = ref(0);

const handleClick = () => {
  // 批量更新,触发 watch 一次
  count.value++;
  count.value++;
  count.value++;
};

watch(count, (newVal, oldVal) => {
  console.log("watch count 值", newVal, oldVal);
});
</script>

第一次执行 count.vauel++

  • 获取 count.value
  • 执行 count.value 自增,触发 countRef 的 dep.trigger,然后执行 dep.notify,这里会进行批处理,startBatch,执行相应 effect.notify将副作用实例加入队列 batchedSub),结束处理 批处理结束 endBatch
  • endBatch 会执行 effect.tigger 方法,由于 watch effect实例有 调度器 scheduler,因此执行 effect.scheduler.
  • 调度器执行 queueJob 函数 任务 job 加入 队列 queue,标记 job.flag 为已加入队列。暂不执行。
  • 执行 queueFlush,调度一个微任务队列。

image.png

第二次 执行 count.vauel++

  • 获取 count.value
  • 执行 count.value 自增,触发 countRef 的 dep.trigger,然后执行 dep.notify,这里会进行批处理,startBatch,执行相应 effect.notify将副作用实例加入队列 batchedSub),结束处理 批处理结束 endBatch
  • endBatch 会执行 effect.tigger 方法,由于 watch effect实例有 调度器 scheduler,因此执行 effect.scheduler.
  • 调度器执行 queueJob 函数 任务 job 加入 队列 queue,由于job 已在队列中,不再重复加入。

第三次 执行 count.vauel++

  • 获取 count.value
  • 执行 count.value 自增,触发 countRef 的 dep.trigger,然后执行 dep.notify,这里会进行批处理,startBatch,执行相应 effect.notify将副作用实例加入队列 batchedSub),结束处理 批处理结束 endBatch
  • endBatch 会执行 effect.tigger 方法,由于 watch effect实例有 调度器 scheduler,因此执行 effect.scheduler.
  • 调度器执行 queueJob 函数 任务 job 加入 队列 queue,由于job 已在队列中,不再重复加入。
  • 微任务执行 flushJobs,按顺序执行任务队列,先执行主队列 queue,再执行后置任务队列 pendingPostFlushCbs,最后还会检查是否有新任务入队,有则递归处理。

image.png

image.png

image.png

枚举 SchedulerJobFlags

enum SchedulerJobFlags {
  QUEUED = 1 << 0, // 1 标记已经加入队列
  PRE = 1 << 1, // 2 标记任务在 DOM 更新前 执行
  ALLOW_RECURSE = 1 << 2, // 4 允许自身递归
  DISPOSED = 1 << 3, // 已被销毁或已取消
}

track 源码

reactive 对象读取属性,依赖收集。

function track(target: object, type: TrackOpTypes, key: unknown): void {
  // 前置条件:是否需要收集依赖
  // - shouldTrack:全局标志,控制是否允许追踪依赖。
  // - activeSub:当前正在执行的 ReactiveEffect 实例(副作用)
  if (shouldTrack && activeSub) {
    // targetMap:全局 Map,存储所有响应式对象的依赖映射
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 如果该 target 第一次被追踪,则创建一个新的 Map 并存入 targetMap。
      targetMap.set(target, (depsMap = new Map()))
    }

    // 获取或创建依赖对象
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = new Dep()))
      dep.map = depsMap // 存储依赖映射表,用于触发依赖时快速查找
      dep.key = key // 存储属性名,用于触发依赖时快速查找
    }
    if (__DEV__) {
      dep.track({
        target,
        type,
        key,
      })
    } else {
      // 收集依赖
      dep.track()
    }
  }
}

trigger 源码

function trigger(
  target: object, // 原始对象(未被代理的原对象)
  type: TriggerOpTypes, // 操作类型:SET | ADD | DELETE | CLEAR
  key?: unknown, // 被修改的属性名
  newValue?: unknown, // 新值
  oldValue?: unknown, // 旧值
  oldTarget?: Map<unknown, unknown> | Set<unknown>, // 用于集合(Map/Set)的旧对象
): void {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    // 如果 target 从未被追踪过(没有 depsMap),则仅增加全局版本号
    globalVersion++
    return
  }

  const run = (dep: Dep | undefined) => {
    if (dep) {
      if (__DEV__) {
        dep.trigger({
          target,
          type,
          key,
          newValue,
          oldValue,
          oldTarget,
        })
      } else {
        // 触发依赖,进行通知
        dep.trigger()
      }
    }
  }

  // 批处理期间会收集需要执行的 effect,最后统一执行。
  // 开始批处理
  startBatch()

  // 1、清空集合,如 Map.clear() / Set.clear()
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    depsMap.forEach(run)
  } else {
    const targetIsArray = isArray(target)
    const isArrayIndex = targetIsArray && isIntegerKey(key)

    // 2、处理数组的 length 属性变化(特殊逻辑)
    if (targetIsArray && key === 'length') {
      const newLength = Number(newValue)
      depsMap.forEach((dep, key) => {
        if (
          key === 'length' ||
          key === ARRAY_ITERATE_KEY ||
          (!isSymbol(key) && key >= newLength)
        ) {
          run(dep)
        }
      })
    } else {
      // schedule runs for SET | ADD | DELETE
      // 如果 key 是具体的属性名(不是 undefined),则触发该属性对应的依赖。
      // 或者存在一个无 key 的依赖(void 0),也需要触发
      // 例如 watchEffect 中直接读取整个 reactive 对象,依赖会挂在 void 0 上。
      if (key !== void 0 || depsMap.has(void 0)) {
        run(depsMap.get(key))
      }

      // schedule ARRAY_ITERATE for any numeric key change (length is handled above)
      // 如果当前 key 是数组索引(key 为整数),还需要触发数组迭代器依赖
      if (isArrayIndex) {
        run(depsMap.get(ARRAY_ITERATE_KEY))
      }

      // also run for iteration key on ADD | DELETE | Map.SET
      switch (type) {
        // 添加新属性
        case TriggerOpTypes.ADD:
          if (!targetIsArray) {
            // 非数组时:触发 ITERATE_KEY(因为 for...in 循环会新增键)
            run(depsMap.get(ITERATE_KEY))
            if (isMap(target)) {
              // 如果是 Map:触发 MAP_KEY_ITERATE_KEY(Map.keys() 迭代器)
              run(depsMap.get(MAP_KEY_ITERATE_KEY))
            }
          } else if (isArrayIndex) {
            // new index added to array -> length changes
            // 如果是数组且是数组索引(添加新索引):触发 length 的依赖(因为数组长度增加了)
            run(depsMap.get('length'))
          }
          break
        // 删除属性
        case TriggerOpTypes.DELETE:
          if (!targetIsArray) {
            run(depsMap.get(ITERATE_KEY))
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY))
            }
          }
          break
        // 设置属性值
        case TriggerOpTypes.SET:
          if (isMap(target)) {
            // 仅当是 Map 时:触发 ITERATE_KEY
            // 因为 Map 的 forEach 或 entries 迭代器需要知道值变化
            run(depsMap.get(ITERATE_KEY))
          }
          break
      }
    }
  }

  endBatch()
}

Dep 源码

class Dep {
  version = 0
  
  activeLink?: Link = undefined
  subs?: Link = undefined
  subsHead?: Link
  
  map?: KeyToDepMap = undefined
  key?: unknown = undefined
  sc: number = 0

  readonly __v_skip = true

  constructor(public computed?: ComputedRefImpl | undefined) {
    if (__DEV__) {
      this.subsHead = undefined
    }
  }

  // 示例:当执行 computed.value会触发依赖收集
  // 示例:当执行 ref.value 会触发依赖收集
  // 建立当前活动的副作用(activeSub)与当前依赖(Dep)之间的双向链接,
  track(debugInfo?: DebuggerEventExtraInfo): Link | undefined {
    // 一、当满足以下任一条件时,会直接返回,不进行依赖追踪
    // 1、没有活动的订阅者(没有正在运行的副作用(例如在非响应式上下文中访问),不收集)
    // 2、不跟踪依赖
    // 3、订阅者是否为当前依赖关联的计算属性。
    //    作用:防止计算属性的循环依赖。场景:当计算属性的 getter 函数访问自身时,避免形成循环依赖。
    if (!activeSub || !shouldTrack || activeSub === this.computed) {
      return
    }

    // 二、获取或创建当前 dep 与 activeSub 的链接(Link)
    let link = this.activeLink // 当前活动 link

    // 没有活动link、或 活动link 不是当前订阅者
    // 作用:确保只为当前订阅者建立链接,避免重复或错误的链接
    if (link === undefined || link.sub !== activeSub) {
      // 创建新的 Link 实例
      link = this.activeLink = new Link(activeSub, this)

      // add the link to the activeEffect as a dep (as tail)
      // 将 link 添加到 订阅者的依赖链表
      if (!activeSub.deps) {
        activeSub.deps = activeSub.depsTail = link
      } else {
        // 2、如果链表中已经有节点,将 link 添加到链表尾尾
        link.prevDep = activeSub.depsTail
        activeSub.depsTail!.nextDep = link
        activeSub.depsTail = link
      }

      // 将链接添加到依赖者的订阅者链表
      addSub(link)

      // link 版本-1 ,说明这是第一次访问依赖,需要同步版本号
    } else if (link.version === -1) {
      // reused from last run - already a sub, just sync version
      link.version = this.version

      // If this dep has a next, it means it's not at the tail - move it to the
      // tail. This ensures the effect's dep list is in the order they are
      // accessed during evaluation.
      if (link.nextDep) {
        const next = link.nextDep
        next.prevDep = link.prevDep
        if (link.prevDep) {
          link.prevDep.nextDep = next
        }

        link.prevDep = activeSub.depsTail
        link.nextDep = undefined
        activeSub.depsTail!.nextDep = link
        activeSub.depsTail = link

        // this was the head - point to the new head
        if (activeSub.deps === link) {
          activeSub.deps = next
        }
      }
    }

    if (__DEV__ && activeSub.onTrack) {
      activeSub.onTrack(
        extend(
          {
            effect: activeSub,
          },
          debugInfo,
        ),
      )
    }

    return link
  }

  // 触发依赖更新
  trigger(debugInfo?: DebuggerEventExtraInfo): void {
    this.version++ // 增加依赖的版本号
    globalVersion++ // 增加全局版本号
    // 通知订阅者
    this.notify(debugInfo)
  }

  notify(debugInfo?: DebuggerEventExtraInfo): void {
    // 开始批处理
    startBatch()
    try {
      if (__DEV__) {
        // subs are notified and batched in reverse-order and then invoked in
        // original order at the end of the batch, but onTrigger hooks should
        // be invoked in original order here.
        for (let head = this.subsHead; head; head = head.nextSub) {
          if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
            head.sub.onTrigger(
              extend(
                {
                  effect: head.sub,
                },
                debugInfo,
              ),
            )
          }
        }
      }
      for (let link = this.subs; link; link = link.prevSub) {
        if (link.sub.notify()) {
          ;(link.sub as ComputedRefImpl).dep.notify()
        }
      }
    } finally {
      endBatch()
    }
  }
}

Link 源码

class Link {

  version: number

  nextDep?: Link
  prevDep?: Link
  nextSub?: Link
  prevSub?: Link
  prevActiveLink?: Link // 存储之前的活动链接

  constructor(
    public sub: Subscriber, // 作为当前实例的sub
    public dep: Dep, // 作为当前实例的dep
  ) {
    this.version = dep.version // 初始化为当前依赖的版本号
    this.nextDep =
      this.prevDep =
      this.nextSub =
      this.prevSub =
      this.prevActiveLink =
        undefined
  }
}

ReactiveEffect 源码

class ReactiveEffect<T = any>
  implements Subscriber, ReactiveEffectOptions
{
  deps?: Link = undefined
  depsTail?: Link = undefined

  flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING

  next?: Subscriber = undefined

  cleanup?: () => void = undefined
  scheduler?: EffectScheduler = undefined
  
  onStop?: () => void

  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void

  constructor(public fn: () => T) {
    if (activeEffectScope && activeEffectScope.active) {
      activeEffectScope.effects.push(this)
    }
  }

  pause(): void {
    this.flags |= EffectFlags.PAUSED
  }

  // 恢复当前 effect 实例
  resume(): void {
    if (this.flags & EffectFlags.PAUSED) {
      this.flags &= ~EffectFlags.PAUSED
      if (pausedQueueEffects.has(this)) {
        pausedQueueEffects.delete(this)
        this.trigger()
      }
    }
  }

  /**
   * @internal
   */
  notify(): void {
    if (
      // 当前 effect 实例正在运行中,且不允许递归调用
      this.flags & EffectFlags.RUNNING &&
      !(this.flags & EffectFlags.ALLOW_RECURSE)
    ) {
      return
    }
    // 当前 effect 实例未被通知过
    if (!(this.flags & EffectFlags.NOTIFIED)) {
      batch(this) // 加入订阅者队列
    }
  }

  run(): T {
    // TODO cleanupEffect

    if (!(this.flags & EffectFlags.ACTIVE)) {
      // stopped during cleanup
      // 调用当前 effect 实例的 fn 函数
      return this.fn()
    }

    // 标记当前 effect 实例为正在运行
    this.flags |= EffectFlags.RUNNING
    cleanupEffect(this) // 清理当前 effect 实例的依赖链表
    prepareDeps(this) // 准备当前 effect 实例的依赖链表
    const prevEffect = activeSub
    const prevShouldTrack = shouldTrack

    activeSub = this // 设置当前实例为活跃订阅者
    shouldTrack = true // 标记当前实例为正在追踪依赖

    try {
      // 调用当前 effect 实例的 fn 函数
      return this.fn()
    } finally {
      if (__DEV__ && activeSub !== this) {
        warn(
          'Active effect was not restored correctly - ' +
            'this is likely a Vue internal bug.',
        )
      }
      cleanupDeps(this)
      activeSub = prevEffect // 恢复上一个活跃订阅者
      shouldTrack = prevShouldTrack // 恢复上一个 shouldTrack 状态
      // 移除当前 effect 实例正在运行的标志位
      this.flags &= ~EffectFlags.RUNNING
    }
  }

  // 停止当前 effect 实例
  stop(): void {
    // 如果当前 effect 实例正在运行中
    if (this.flags & EffectFlags.ACTIVE) {
      // 移除当前 effect 实例的所有依赖
      for (let link = this.deps; link; link = link.nextDep) {
        removeSub(link)
      }
      // 清理当前 effect 实例的依赖链表
      this.deps = this.depsTail = undefined
      // 清理当前 effect 实例的依赖链表
      cleanupEffect(this)
      // 调用当前 effect 实例的 onStop 回调
      this.onStop && this.onStop()
      // 移除当前 effect 实例活跃的标志位
      this.flags &= ~EffectFlags.ACTIVE
    }
  }

  // 触发当前 effect 执行,根据调度器函数或直接运行
  trigger(): void {
    // 如果当前 effect 实例正在暂停中
    if (this.flags & EffectFlags.PAUSED) {
      // 将当前 effect 实例添加到暂停队列中
      pausedQueueEffects.add(this)

      // 调用当前 effect 实例的 scheduler 函数
    } else if (this.scheduler) {
      this.scheduler()
    } else {
      // 如果当前 effect 实例脏了
      this.runIfDirty()
    }
  }

  /**
   * @internal
   */
  runIfDirty(): void {
    if (isDirty(this)) {
      this.run()
    }
  }

  get dirty(): boolean {
    return isDirty(this)
  }
}

最后