1. 基本概念

Object.defineProperty

  • ES5 引入的方法,用于在对象上定义新属性或修改现有属性
  • 可以精确控制属性的特性(可枚举、可配置、可写等)

Proxy

  • ES6 引入的特性,用于创建一个对象的代理
  • 可以拦截和自定义对象的各种操作(属性查找、赋值、枚举等)

2. 在 Vue 中的应用关系

Vue 2 使用 defineProperty

// Vue 2 响应式原理简化版
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`获取 ${key}: ${val}`)
      // 依赖收集
      return val
    },
    set(newVal) {
      console.log(`设置 ${key}: ${newVal}`)
      if (newVal !== val) {
        val = newVal
        // 触发更新
      }
    }
  })
}
 
const data = {}
defineReactive(data, 'name', 'Vue')

Vue 3 使用 Proxy

// Vue 3 响应式原理简化版
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      console.log(`获取 ${key}`)
      // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      console.log(`设置 ${key}: ${value}`)
      const result = Reflect.set(target, key, value, receiver)
      // 触发更新
      return result
    }
  })
}
 
const data = reactive({ name: 'Vue' })

3. 主要差异对比

特性definePropertyProxy
兼容性IE8+IE不支持,现代浏览器
监听范围单个属性整个对象
新增属性无法监听可以监听
删除属性无法监听可以监听
数组变化需要特殊处理原生支持
性能需要递归遍历惰性代理

4. 具体差异详解

新增/删除属性

// defineProperty 的局限
const obj = {}
Object.defineProperty(obj, 'name', {
  get() { return this._name },
  set(val) { this._name = val }
})
 
obj.name = 'test' // 可以监听
obj.age = 25      // 无法监听到新增属性
 
// Proxy 的优势
const proxyObj = new Proxy({}, {
  set(target, key, value) {
    console.log(`设置 ${key}: ${value}`)
    target[key] = value
    return true
  }
})
 
proxyObj.name = 'test' // 可以监听
proxyObj.age = 25      // 也可以监听到

数组处理

// defineProperty 对数组的处理
// Vue 2 需要重写数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
arrayMethods.forEach(method => {
  Array.prototype[method] = function(...args) {
    console.log(`数组方法 ${method} 被调用`)
    // 触发更新
    return originalMethod.apply(this, args)
  }
})
 
// Proxy 可以直接监听数组
const arr = new Proxy([], {
  set(target, key, value) {
    console.log(`数组索引 ${key} 被设置为 ${value}`)
    target[key] = value
    return true
  }
})

5. 性能对比

defineProperty

  • 需要在初始化时递归遍历所有属性
  • 每个属性都需要单独定义 getter/setter
  • 对深层嵌套对象性能影响较大

Proxy

  • 惰性代理,只有访问时才会触发
  • 可以代理整个对象,无需遍历属性
  • 对深层对象有更好的性能表现

6. 实际应用场景

defineProperty 适用场景

  • 需要兼容老版本浏览器
  • 只需要监听特定属性
  • 对性能要求不高的简单场景

Proxy 适用场景

  • 现代浏览器环境
  • 需要监听对象的所有变化
  • 复杂的响应式系统(如 Vue 3)
  • 需要拦截多种操作类型

7. 代码示例对比

// defineProperty 实现简单响应式
function observeWithDefineProperty(obj) {
  Object.keys(obj).forEach(key => {
    let val = obj[key]
    Object.defineProperty(obj, key, {
      get() {
        console.log(`读取 ${key}`)
        return val
      },
      set(newVal) {
        console.log(`修改 ${key} 为 ${newVal}`)
        val = newVal
      }
    })
  })
}
 
// Proxy 实现响应式
function observeWithProxy(obj) {
  return new Proxy(obj, {
    get(target, key) {
      console.log(`读取 ${key}`)
      return target[key]
    },
    set(target, key, value) {
      console.log(`修改 ${key} 为 ${value}`)
      target[key] = value
      return true
    },
    deleteProperty(target, key) {
      console.log(`删除 ${key}`)
      delete target[key]
      return true
    }
  })
}