Vue2 Object.defineProperty 响应式原理

Vue2 如何通过 Object.defineProperty 实现响应式,以及其局限。

#type / concept #status / growing #tech / dev / frame #resource / vue2

[!info] related notes

Vue2 Object.defineProperty 响应式原理

一句话定义

Vue2 在初始化阶段递归遍历 data 对象每个属性,用 Object.defineProperty 把它们改造成 getter/setter,在 getter 中收集依赖、setter 中触发更新。

核心内容

依赖收集:Dep + Watcher

Vue2 的依赖模型由三个核心角色组成:

  • Dep:每个响应式属性的”订阅者容器”,负责收集和通知 Watcher
  • Watcher:需要在数据变化时重新执行的任务(组件渲染、computed、watch)
  • Dep.target:全局指针,指向当前正在执行的 Watcher
function defineReactive(obj, key, val) {
  const dep = new Dep()
  
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      dep.notify()
    }
  })
}

初始化流程

function observe(data) {
  if (!isObject(data)) return
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key])
    if (isObject(data[key])) {
      observe(data[key]) // 递归处理深层对象
    }
  })
}

特点:初始化时深度遍历 + 属性劫持

数组的特殊处理

Object.defineProperty 无法拦截数组索引赋值和 length 修改,所以 Vue2 重写了数组的变异方法:

const proto = Array.prototype
const arrayMethods = Object.create(proto)
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  const original = proto[method]
  arrayMethods[method] = function(...args) {
    const result = original.apply(this, args)
    dep.notify() // 通知更新
    return result
  }
})

边界与易混淆点

Vue2 响应式的局限

场景Vue2 表现
新增属性不会响应,需用 Vue.set
删除属性不会响应,需用 Vue.delete
arr[index] = value不触发更新
arr.length = 0不触发更新
Map / Set支持差

为什么要用 Vue.set / Vue.delete

因为新增/删除属性没有对应的 getter/setter,Vue2 无法拦截这些操作。Vue.set 的实现原理:

Vue.set(target, key, value) {
  defineReactive(target, key, value)
  target.__ob__.dep.notify() // 触发更新
}

和 Vue3 Proxy 的本质区别

  • Vue2:预先劫持每个属性,初始化成本高
  • Vue3:运行时代理整个对象,惰性初始化
创建于 2026/4/7 更新于 2026/5/27