Object.defineProperty vs Proxy 对比总结
一、基本区别
| 特性 | Object.defineProperty | Proxy |
|---|---|---|
| 出现时间 | ES5 (2009) | ES6 (2015) |
| 监听范围 | 单个属性 | 整个对象 |
| API复杂度 | 较复杂 | 更简洁 |
| 性能 | 较好 | 稍差(但可接受) |
| 浏览器支持 | 几乎全部 | IE不支持 |
二、核心能力对比
1. 监听范围
javascript
// defineProperty - 需要逐个属性定义
const obj = { name: '张三', age: 18 }
Object.defineProperty(obj, 'name', { get() {...}, set() {...} })
Object.defineProperty(obj, 'age', { get() {...}, set() {...} })
// 新增属性无法监听
// Proxy - 监听整个对象
const proxy = new Proxy(obj, {
get(target, key) {...},
set(target, key, value) {...}
})
// 所有属性(包括新增)都能监听
2. 数组监听
javascript
// defineProperty - 无法直接监听数组变化
const arr = [1, 2, 3]
// 需要重写数组方法才能实现
// Proxy - 完美支持数组
const proxyArr = new Proxy(arr, {
set(target, key, value) {
console.log(`设置索引${key}: ${value}`)
target[key] = value
return true
}
})
proxyArr.push(4) // 可以监听到
3. 嵌套监听
javascript
// defineProperty - 需要递归遍历
function observe(obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
observe(obj[key]) // 递归
}
defineReactive(obj, key)
})
}
// Proxy - 惰性代理
const proxy = new Proxy(obj, {
get(target, key) {
const val = target[key]
// 只在访问时才代理对象
if (typeof val === 'object') {
return new Proxy(val, handler)
}
return val
}
})
三、功能对比
| 功能 | defineProperty | Proxy |
|---|---|---|
| 监听属性读取 | ✅ get | ✅ get |
| 监听属性赋值 | ✅ set | ✅ set |
| 监听属性删除 | ❌ | ✅ deleteProperty |
| 监听函数调用 | ❌ | ✅ apply |
| 监听构造函数 | ❌ | ✅ construct |
| 监听in操作符 | ❌ | ✅ has |
| 监听for...in | ❌ | ✅ ownKeys |
| 监听属性定义 | ❌ | ✅ defineProperty |
四、实际代码对比
Vue 2 vs Vue 3 响应式
javascript
// Vue 2 - defineProperty
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`获取${key}: ${val}`)
return val
},
set(newVal) {
console.log(`设置${key}: ${newVal}`)
val = newVal
}
})
}
// Vue 3 - Proxy
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
console.log(`获取${String(key)}`)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
console.log(`设置${String(key)}: ${value}`)
return res
}
})
}
五、优缺点总结
Object.defineProperty
优点:
- 性能较好
- 兼容性好(IE9+)
- 可精确控制单个属性
缺点:
- 无法监听数组变化(需要hack)
- 无法监听新增/删除属性
- 需要递归遍历对象
- API不够直观
Proxy
优点:
- 监听能力强(13种拦截器)
- 支持数组、对象新增属性
- 无需递归(惰性代理)
- API更简洁
- 可以代理多种数据结构
缺点:
- 兼容性稍差(无IE)
- 性能略低于defineProperty
- 无法polyfill完全
六、使用场景建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单对象监听 | defineProperty | 性能好,足够用 |
| 复杂响应式系统 | Proxy | 功能强大,维护简单 |
| 需要兼容IE | defineProperty | Proxy不支持IE |
| 数组操作监听 | Proxy | defineProperty需要hack |
| 动态属性监听 | Proxy | defineProperty无法监听新增属性 |
| 性能敏感场景 | defineProperty | 执行效率更高 |
七、最佳实践示例
javascript
// 简单属性监听 - 用defineProperty
const formData = {}
Object.defineProperty(formData, 'username', {
set(val) {
console.log('输入验证:', val)
this._username = val
}
})
// 复杂响应式 - 用Proxy
const reactive = (obj) => {
return new Proxy(obj, {
get(target, key) {
if (typeof target[key] === 'object') {
return reactive(target[key]) // 递归代理
}
return Reflect.get(target, key)
},
set(target, key, value) {
console.log(`更新视图: ${key}=${value}`)
return Reflect.set(target, key, value)
}
})
}
总结
- defineProperty:简单、性能好、兼容性强,但功能受限
- Proxy:强大、灵活、语义清晰,但需要现代浏览器
Vue 3 选择 Proxy 是因为它能更好地处理动态属性、数组等复杂场景,代码更简洁。如果不需要兼容IE,Proxy是更好的选择。