Vue 1.x 响应式系统

Vue 1.x 如何通过 Object.defineProperty 实现数据响应式

问题

Vue 1.x 的响应式系统是如何实现的?它有什么特点?

解答

Vue 1.x 使用 Object.defineProperty 对数据进行劫持,配合 Dep(依赖收集器)和 Watcher(观察者)实现响应式更新。

整体架构

Data --> Observer --> Dep <--> Watcher --> DOM
  • Observer:遍历数据,用 Object.defineProperty 转换为 getter/setter
  • Dep:每个属性对应一个 Dep,收集依赖该属性的 Watcher
  • Watcher:每个 DOM 绑定对应一个 Watcher,属性变化时更新 DOM

简化实现

// 依赖收集器
class Dep {
  constructor() {
    this.subs = [] // 存储 Watcher
  }
  
  addSub(watcher) {
    this.subs.push(watcher)
  }
  
  notify() {
    // 通知所有 Watcher 更新
    this.subs.forEach(watcher => watcher.update())
  }
}

// 全局变量,用于收集依赖时标记当前 Watcher
Dep.target = null

// 观察者
class Watcher {
  constructor(vm, key, callback) {
    this.vm = vm
    this.key = key
    this.callback = callback
    
    // 触发 getter,收集依赖
    Dep.target = this
    this.value = vm[key]
    Dep.target = null
  }
  
  update() {
    const newValue = this.vm[this.key]
    if (newValue !== this.value) {
      this.value = newValue
      this.callback(newValue)
    }
  }
}

// 将数据转换为响应式
function defineReactive(obj, key, val) {
  const dep = new Dep()
  
  Object.defineProperty(obj, key, {
    get() {
      // 收集依赖
      if (Dep.target) {
        dep.addSub(Dep.target)
      }
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      // 通知更新
      dep.notify()
    }
  })
}

// 遍历对象,转换所有属性
function observe(obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

使用示例

// 创建数据
const data = { message: 'Hello', count: 0 }

// 转换为响应式
observe(data)

// 创建 Watcher,模拟 DOM 绑定
new Watcher(data, 'message', (val) => {
  console.log('message 更新:', val)
  // 实际场景:更新 DOM
})

new Watcher(data, 'count', (val) => {
  console.log('count 更新:', val)
})

// 修改数据,自动触发更新
data.message = 'World'  // 输出: message 更新: World
data.count = 1          // 输出: count 更新: 1

Vue 1.x 的特点

Vue 1.x 采用细粒度依赖追踪:每个 DOM 节点绑定都有独立的 Watcher。

// 模板
// <p>{{ name }}</p>
// <p>{{ age }}</p>
// <p>{{ name }} - {{ age }}</p>

// Vue 1.x 会创建 3 个 Watcher
// Watcher1 -> 依赖 name
// Watcher2 -> 依赖 age  
// Watcher3 -> 依赖 name, age

这种方式更新精确,但 Watcher 数量多,内存开销大。

关键点

  • Object.defineProperty:通过 getter/setter 拦截数据读写
  • Dep:每个属性一个 Dep,存储依赖该属性的 Watcher
  • Watcher:每个 DOM 绑定一个 Watcher,数据变化时更新对应 DOM
  • 细粒度追踪:更新精确但 Watcher 多,内存占用高
  • 局限性:无法检测属性新增/删除、数组索引修改