Proxy 和 Reflect
1. Proxy(代理)
什么是 Proxy?
Proxy 是 ES6 引入的元编程特性,用于创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性访问、赋值、枚举等)。
基本语法
const proxy = new Proxy(target, handler)
核心 handler 方法(陷阱)
| 方法 | 触发时机 | 用途 |
|---|---|---|
get(target, prop, receiver) | 读取属性 | 拦截属性访问 |
set(target, prop, value, receiver) | 设置属性 | 拦截属性赋值 |
has(target, prop) | in操作符 | 拦截 in检查 |
deleteProperty(target, prop) | delete操作 | 拦截属性删除 |
ownKeys(target) | Object.keys()等 | 拦截键枚举 |
construct(target, args) | new操作 | 拦截构造函数调用 |
apply(target, thisArg, args) | 函数调用 | 拦截函数调用 |
使用示例
const target = { name: '小明', age: 20 }
const handler = {
get(target, prop) {
console.log(`读取属性: ${prop}`)
return prop in target ? target[prop] : '属性不存在'
},
set(target, prop, value) {
console.log(`设置属性: ${prop} = ${value}`)
// 添加验证逻辑
if (prop === 'age' && (value < 0 || value > 150)) {
throw new Error('年龄无效')
}
target[prop] = value
return true // 表示成功
},
deleteProperty(target, prop) {
console.log(`删除属性: ${prop}`)
delete target[prop]
return true
}
}
const proxy = new Proxy(target, handler)
console.log(proxy.name) // 输出: 读取属性: name → 小明
proxy.age = 25 // 输出: 设置属性: age = 25
delete proxy.name // 输出: 删除属性: name
2. Reflect(反射)
什么是 Reflect?
Reflect 是一个内置对象,提供了一套操作对象的方法,这些方法与 Proxy 的 handler 方法一一对应。
为什么需要 Reflect?
- 统一操作:将对象的操作(如
Object.defineProperty、delete obj[key])统一为函数形式 - 默认行为:为 Proxy 的 handler 提供默认实现
- 更好的返回值:提供更合理的返回值(如
Reflect.defineProperty返回布尔值)
Reflect 的常用方法
| 方法 | 对应操作 | 说明 |
|---|---|---|
Reflect.get(target, prop, receiver) | target[prop] | 获取属性 |
Reflect.set(target, prop, value, receiver) | target[prop] = value | 设置属性 |
Reflect.has(target, prop) | prop in target | 判断属性 |
Reflect.deleteProperty(target, prop) | delete target[prop] | 删除属性 |
Reflect.ownKeys(target) | Object.keys(target) | 获取所有键 |
Reflect.construct(target, args) | new target(...args) | 构造函数调用 |
3. Proxy 和 Reflect 的配合使用
最佳实践:在 Proxy 中使用 Reflect
const target = { count: 0 }
const handler = {
get(target, prop, receiver) {
console.log(`get ${prop}`)
// 使用 Reflect 调用默认行为
return Reflect.get(target, prop, receiver)
},
set(target, prop, value, receiver) {
console.log(`set ${prop} = ${value}`)
// 使用 Reflect 调用默认行为
return Reflect.set(target, prop, value, receiver)
}
}
const proxy = new Proxy(target, handler)
为什么要配合使用?
- 保持默认行为:Reflect 方法执行了对象的默认操作
- 正确处理 receiver:确保
this指向正确 - 简化代码:避免手动实现默认逻辑
// ❌ 不推荐的写法
const handler = {
get(target, prop) {
return target[prop] // 丢失了 this 绑定
}
}
// ✅ 推荐的写法
const handler = {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver) // 保持正确的 this
}
}
4. 实际应用场景
场景1:数据验证
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number' || value < 0) {
throw new Error('年龄必须是正数')
}
}
return Reflect.set(target, prop, value)
}
}
const person = new Proxy({}, validator)
person.age = 25 // 成功
person.age = -5 // 抛出错误
场景2:属性观察
function observe(obj, callback) {
return new Proxy(obj, {
set(target, prop, value, receiver) {
const oldValue = target[prop]
const result = Reflect.set(target, prop, value, receiver)
if (oldValue !== value) {
callback(prop, oldValue, value)
}
return result
}
})
}
const data = observe({ count: 0 }, (prop, oldVal, newVal) => {
console.log(`${prop} 从 ${oldVal} 变为 ${newVal}`)
})
data.count = 1 // 输出: count 从 0 变为 1
场景3:实现简单的 Vue3 响应式
// 简化版的 reactive
function reactive(target) {
const handler = {
get(target, prop, receiver) {
console.log(`收集依赖: ${String(prop)}`)
const result = Reflect.get(target, prop, receiver)
// 深层代理
if (result && typeof result === 'object') {
return reactive(result)
}
return result
},
set(target, prop, value, receiver) {
const oldValue = target[prop]
const result = Reflect.set(target, prop, value, receiver)
if (oldValue !== value) {
console.log(`触发更新: ${String(prop)} = ${value}`)
}
return result
}
}
return new Proxy(target, handler)
}
const state = reactive({
user: {
name: '小明',
age: 20
}
})
console.log(state.user.name) // 收集依赖: user → 收集依赖: name
state.user.age = 25 // 触发更新: age = 25
5. 重要特性对比
| 特性 | Object.defineProperty(Vue2) | Proxy(Vue3) |
|---|---|---|
| 监听范围 | 只能监听属性,需遍历 | 可监听整个对象 |
| 监听操作 | 只能监听 get/set | 可监听13种操作 |
| 动态添加 | 需用 Vue.set | 直接监听 |
| 数组监听 | 需重写方法 | 直接监听索引和长度 |
| 性能 | 初始化递归,性能较差 | 惰性代理,性能更优 |
6. 在 Vue 3 响应式中的应用
Vue 3 的响应式系统核心就是 Proxy + Reflect:
// Vue 3 reactive 的简化实现
const reactiveMap = new WeakMap()
function reactive(target) {
// 已经是代理则直接返回
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
const handler = {
get(target, key, receiver) {
// 使用 Reflect 获取值
const res = Reflect.get(target, key, receiver)
// 收集依赖
track(target, key)
// 如果是对象,继续代理
if (typeof res === 'object' && res !== null) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
// 触发更新
trigger(target, key)
}
return result
}
}
const proxy = new Proxy(target, handler)
reactiveMap.set(target, proxy)
return proxy
}
总结
-
Proxy 是拦截器:用于拦截和自定义对象的操作
-
Reflect 是操作器:提供操作对象的标准化方法
-
配合使用:在 Proxy 的 handler 中使用 Reflect 调用默认行为
-
优势:
- 功能更强大(可拦截13种操作)
- 性能更优(惰性代理)
- 支持数组和动态属性
-
在 Vue 3 中:Proxy + Reflect 实现了更完善的响应式系统
简单记忆:
- Proxy = "你想做什么,我都要先过问一下"
- Reflect = "我来帮你执行默认操作"
- 最佳组合:Proxy 负责拦截,Reflect 负责执行默认行为