标准答案

  1. 用 reactive 包装对象,返回一个 Proxy。
  2. 读取属性时执行 track,把当前 effect 记录到这个对象属性对应的依赖集合里。
  3. 修改属性时执行 trigger,找到依赖集合并重新运行相关 effect。
  4. 用 WeakMap 保存 target 到依赖表的关系,避免强引用阻止对象回收。
  5. 真实系统还要处理嵌套对象、数组、computed、watch、调度、循环触发和清理依赖。

题目解析

响应式系统的关键是建立“读取时订阅,修改时通知”的关系。effect 执行期间读取了哪些属性,这些属性变化时就能找到对应 effect 重新执行。

依赖表通常用 WeakMap -> Map -> Set 表达。WeakMap 的 key 是原始对象,Map 的 key 是属性名,Set 里存 effect 函数。

简化版可以帮助理解原理,但真实框架会复杂很多。比如 Vue 需要避免重复收集、支持 scheduler、处理数组长度变化、computed 缓存和组件更新批处理。

代码示例

最小响应式模型可以展示 track 和 trigger 的关系。

JavaScript
const bucket = new WeakMap()
let activeEffect

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = undefined
}

function track(target, key) {
  if (!activeEffect) return

  let depsMap = bucket.get(target)
  if (!depsMap) {
    depsMap = new Map()
    bucket.set(target, depsMap)
  }

  let effects = depsMap.get(key)
  if (!effects) {
    effects = new Set()
    depsMap.set(key, effects)
  }

  effects.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = bucket.get(target)
  const effects = depsMap && depsMap.get(key)

  if (effects) {
    effects.forEach((fn) => fn())
  }
}

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      track(obj, key)
      return obj[key]
    },
    set(obj, key, value) {
      obj[key] = value
      trigger(obj, key)
      return true
    }
  })
}

常见误区

  • 只说 Proxy 能监听变化,讲不清依赖是什么时候收集的。
  • 没有用 target 和 key 区分依赖,导致任意属性变化都触发所有 effect。
  • 忽略调度和循环触发,实际更新可能重复执行或死循环。

作者信息