2025.09.16

基础骨架:WeakMap 实现三层依赖存储

🧐 挑战回顾

reactive 模块中,我们面临的核心挑战是如何为对象的每个属性维护一个独立的 Dep(依赖集合),同时确保不污染原始目标对象 target

  • ref 依赖存储: Dep 直接挂载到 Ref 实例上
  • reactive 依赖存储: 多属性, 需要建立一个全局的映射表 来存储 target -> key -> Dep 这种层级关系

🔑 核心解决方案

选择使用 WeakMap 作为顶层容器,来实现目标对象与依赖图的关联。

const targetMap = new WeakMap() // 全局依赖存储容器

为什么使用 WeakMap

理想型选择, 它提供了 非侵入性和内存管理 双重优势:

  • 健必须是对象: WeakMapkey 只能是对象, 天然适用于响应式对象 target 作为键
  • 弱引用: WeakMap 对 键 的引用是 弱引用。如果没有其他变量引用 target, 就会被垃圾回收清理掉, 避免内存泄露

🧱 三层依赖存储结构

层级数据结构Keyvalue职责
1target (WeakMap)原始 target 对象depsMap (Map)将响应式对象映射到其属性的依赖集合。
2depsMap (Map)属性 key (字符串)Dep 实例将属性名映射到该属性的 Dep 实例
3Dep 实例subs / subsTail存储所有依赖与该属性的 effect
// 逻辑视图
// targetMap = WeakMap {
//   { a: 1, b: 2 } => Map {
//     'a' => Dep { subs: link1 -> link3... }, // 管理属性 a 的依赖
//     'b' => Dep { subs: link2 -> link4... }  // 管理属性 b 的依赖
//   }
// }

⚙️ 依赖收集(track)的实现

function track(target: object, key: PropertyKey) {
  if (!activeSub)
    return

  // 1. 查找 target 对应的 depsMap
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap) // 首次访问:创建并存储 Map
  }

  // 2. 查找 key 对应的 Dep
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Dep()
    depsMap.set(key, dep) // 首次访问该 key:创建并存储 Dep
  }

  // 3. 关联:将当前的 effect (activeSub) 链接到 Dep
  link(dep, activeSub)
}

📢 派发更新(trigger)的实现

function trigger(target: object, key: PropertyKey) {
  // 1. 查找 target 对应的 depsMap
  const depsMap = targetMap.get(target)
  if (!depsMap)
    return // target 没有依赖,直接返回

  // 2. 查找 key 对应的 Dep
  const dep = depsMap.get(key)
  if (!dep)
    return // key 没有依赖,直接返回

  // 3. 触发更新:通知所有订阅者重新执行
  if (dep) {
    propagate(dep.subs)
  }
}

🧪 测试一下

<script type="module">
  // ... imports ...
  const state = reactive({ a: 0 })

  effect(() => {
    console.log('state.a ==> ', state.a) // 收集 state.a 的依赖
  })

  setTimeout(() => {
    state.a = 2 // 触发 state.a 的 Dep 通知
  }, 1000)
</script>
  • 初始化的时候 打印 state.a ==> 0
  • 1 秒后 打印 state.a ==> 2