人人都会AI编程

依赖清理与避免重复收集机制

更新时间:2026-07-10

当响应式数据变化时,Vue 需要精准通知到依赖它的副作用函数(effect)。但如果一个 effect 中依赖的数据发生变化,会导致 effect 重新执行,而新的执行可能产生不同的依赖集合。此时旧的依赖如果继续保留,就会造成无效更新甚至内存泄漏。为了解决这个问题,Vue 实现了依赖清理避免重复收集两套机制。

为什么需要清理依赖

考虑以下组件逻辑:

const useToggle = () => {
  const state = reactive({ flag: true, a: 'hello', b: 'world' })
  effect(() => {
    if (state.flag) {
      console.log(state.a)
    } else {
      console.log(state.b)
    }
  })
}

首次执行 effect 时,state.flag 为 true,读取了 state.a,因此该 effect 被收集到 state.a 的依赖集合中。随后如果 state.flag 变为 false,effect 重新执行,这次读取的是 state.b,不再读取 state.a。如果不做清理,那么后续修改 state.a 仍会触发 effect 执行,但此时 effect 的逻辑根本不关心 state.a——这既是浪费,也可能导致预料之外的副作用。

清理机制:在每次执行 effect 前清除旧依赖

Vue 3 的 effect 函数在每次执行前,都会调用一个 cleanup 步骤,将当前 effect 从它之前订阅的所有依赖集合中移除。具体实现方式是:每个 effect 维护一个 deps 数组,记录它当前订阅的所有 Dep(即响应式属性的依赖集合)。当重新执行时,遍历 deps,把该 effect 从每一个 Dep 中删除,然后重新执行,在执行过程中通过 track 重新收集新的依赖。这样旧依赖被清空,新依赖被建立,保证 effect 只在真正需要时被触发。

伪代码表示如下:

function effect(fn) {
  const reactiveEffect = () => {
    cleanup(reactiveEffect)   // 清除旧依赖
    activeEffect = reactiveEffect
    fn()                      // 重新执行,触发 track 重新收集
  }
  reactiveEffect.deps = []    // 存储该 effect 订阅的所有 Dep
  reactiveEffect()
}

function cleanup(effect) {
  effect.deps.forEach(dep => dep.delete(effect))
  effect.deps.length = 0
}

track 函数负责在读取响应式数据时,将当前 activeEffect 添加到该数据对应的 Dep 中,同时将该 Dep 存入 effect 的 deps 数组,形成双向记录。这样 cleanup 时就能精确定位到需要删除的位置。

避免重复收集

同一个 effect 可能多次访问同一个响应式属性,例如:

effect(() => {
  console.log(state.a + state.a)
})

如果不做处理,state.a 的依赖集合中,同一个 effect 会被重复添加两次。一旦 state.a 变化,effect 会被重复调用两次,导致不必要的开销。

Vue 通过以下两种方式防止重复收集:

  1. Set 管理依赖

每个响应式属性维护一个 Dep 实例,它的内部使用 Set 存储 effect。Set 本身就保证了元素不重复。即使多次调用 dep.add(effect),集合中也始终只有一个引用。

  1. track 时的双向检查

在执行 track 时,除了将 effect 加入 Dep,还会检查该 effect 的 deps 数组中是否已经存在相同的 Dep 对象。如果存在,则跳过后续操作,避免重复建立关联。

结合清理机制,每次 effect 执行前会清理所有旧的 Dep 关联,并在新的执行中重建关联。重建过程中 Set 保证了重复访问不会导致重复收集,从而让整个依赖系统始终精确、最小化。

实用价值

这套机制对开发者是透明的,但理解它能帮助避免一些不必要的困扰:

  • 条件分支中的依赖变化会自动调整:我们不需要手动管理依赖,Vue 自动保证 effect 始终只订阅最后一次执行所访问的数据。
  • 不会造成内存泄漏:如果组件销毁时 effect 被停止(通过 effectScope 或组件卸载时的清理),其关联的所有 Dep 都会被移除,响应式数据不再持有该 effect 的引用,GC 可以正常回收。
  • 性能保证:重复收集的防止保证了更新派发时的遍历次数最优,不会出现一个 effect 被多次调用的情况。

简单说,依赖清理让响应式系统拥有“自净”能力,避免陈旧依赖造成无效更新;而重复收集预防则确保每个依赖关系都是干净、单一的。两者配合,让 Vue 3 的响应式既灵活又健壮。