Vue2 响应式系统实现

手写 defineReactive、Dep 和 Watcher

问题

从零实现 Vue2 的简易响应式系统,包含 defineReactiveDepWatcher

解答

实现 Dep(依赖收集器)

// 当前正在执行的 Watcher
Dep.target = null

class Dep {
  constructor() {
    // 存储所有订阅者
    this.subs = []
  }

  // 添加订阅者
  addSub(watcher) {
    this.subs.push(watcher)
  }

  // 收集依赖
  depend() {
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }

  // 通知所有订阅者更新
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

实现 defineReactive

function defineReactive(obj, key, val) {
  // 每个属性都有自己的 Dep 实例
  const dep = new Dep()

  // 递归处理嵌套对象
  observe(val)

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 收集依赖
      dep.depend()
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      // 新值可能是对象,需要递归观察
      observe(newVal)
      // 通知更新
      dep.notify()
    }
  })
}

// 观察对象的所有属性
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return

  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

实现 Watcher

class Watcher {
  constructor(getter, callback) {
    this.getter = getter    // 获取数据的函数
    this.callback = callback // 数据变化时的回调
    this.value = this.get()  // 初始化时触发依赖收集
  }

  get() {
    // 设置当前 Watcher
    Dep.target = this
    // 执行 getter,触发属性的 get,完成依赖收集
    const value = this.getter()
    // 清空当前 Watcher
    Dep.target = null
    return value
  }

  update() {
    const oldValue = this.value
    this.value = this.get()
    this.callback(this.value, oldValue)
  }
}

完整示例

// 测试数据
const data = {
  name: 'Vue',
  info: {
    version: 2
  }
}

// 使数据响应式
observe(data)

// 创建 Watcher 监听 name
new Watcher(
  () => data.name,
  (newVal, oldVal) => {
    console.log(`name 变化: ${oldVal} -> ${newVal}`)
  }
)

// 创建 Watcher 监听 info.version
new Watcher(
  () => data.info.version,
  (newVal, oldVal) => {
    console.log(`version 变化: ${oldVal} -> ${newVal}`)
  }
)

// 测试
data.name = 'Vue2'        // 输出: name 变化: Vue -> Vue2
data.info.version = 3     // 输出: version 变化: 2 -> 3

关键点

  • Dep.target 是全局变量,用于在 getter 中获取当前 Watcher
  • 闭包 让每个属性都有独立的 Dep 实例和 val 值
  • 依赖收集时机:Watcher 初始化时执行 getter,触发属性的 get
  • Object.defineProperty 的局限:无法检测属性的新增和删除,数组索引修改也无法检测
  • 递归观察:嵌套对象和新赋值的对象都需要递归处理