【JavaScript Proxy 与 Reflect 指南】
JavaScript Proxy 与 Reflect 指南
目录
- 前置知识:元编程与反射
- Proxy 核心概念
- Reflect 深度解析
- receiver 参数详解
- getter/setter 与属性描述符
- this 绑定机制
- 实战案例与最佳实践
- 常见误区与问题
1. 前置知识:元编程与反射
1.1 什么是元编程
元编程(Metaprogramming) = 写代码来操控代码本身的行为,而不是直接操作业务数据。
通俗比喻
- 普通编程:你是厨师,直接炒菜(处理数据)
- 元编程:你是餐厅老板,制定"厨师做菜的规则"(改变语言行为)
代码示例
// 普通编程 - 处理业务数据
const price = 100
const total = price * 2// 元编程 - 改变对象访问的规则
const obj = new Proxy({},{get(target, prop) {return prop in target ? target[prop] : '默认值'}}
)obj.anything // '默认值' - 改变了属性访问的规则
1.2 什么是反射(Reflection)
反射 是程序在运行时检查、访问和修改自身结构的能力。就像照镜子(reflect)能看到自己一样。
反射的三大能力
能力 | 说明 | 例子 |
---|---|---|
内省(Introspection) | 检查对象结构 | Reflect.ownKeys(obj) |
动态调用 | 运行时调用方法 | Reflect.apply(fn, thisArg, args) |
动态修改 | 运行时修改结构 | Reflect.defineProperty(obj, prop, desc) |
实际应用
// 检查对象结构(内省)
const user = { name: 'Alice', age: 25 }
Reflect.ownKeys(user) // ['name', 'age']
Reflect.has(user, 'name') // true// 动态调用方法
const methodName = 'toUpperCase' // 运行时才知道的方法名
const result = Reflect.get(String.prototype, methodName)
Reflect.apply(result, 'hello', []) // "HELLO"// 动态修改对象
Reflect.defineProperty(user, 'secret', {value: 'hidden',enumerable: false // 不可枚举
})Object.keys(user) // ['name', 'age'] - secret 被隐藏
user.secret // 'hidden' - 但可以访问
1.3 JavaScript 中的元编程工具
工具 | 作用 | 典型场景 |
---|---|---|
Proxy | 拦截对象操作 | Vue 3、MobX、Immer |
Reflect | 标准化元操作 | 配合 Proxy 转发 |
Object.defineProperty | 定义属性特性 | Vue 2 响应式 |
Symbol | 创建隐藏属性 | 内部状态管理 |
2. Proxy 核心概念
2.1 Proxy 是什么
Proxy = 代理对象,在目标对象前设置"拦截层",可以改变默认行为。
基础语法
const proxy = new Proxy(target, handler)
- target:原始对象
- handler:包含陷阱方法的对象
- proxy:返回的代理对象
2.2 陷阱(Trap)的含义
陷阱 = 拦截点 = handler 对象里的方法
为什么叫"陷阱"?
- 捕获/拦截:像捕兽夹一样"抓住"经过的操作
- 不易察觉:外表看起来像普通对象,访问时才触发
- 改变行为:可以完全改变操作的结果
术语来源
- 操作系统:trap = 系统调用拦截点(陷入内核态)
- Proxy:借鉴了这个概念,拦截对象操作
2.3 13 种陷阱完整列表
陷阱名称 | 拦截的操作 | 对应语法 |
---|---|---|
get | 读取属性 | obj[prop] |
set | 写入属性 | obj[prop] = val |
has | in 运算符 | prop in obj |
deleteProperty | delete 运算符 | delete obj[prop] |
apply | 函数调用 | fn() |
construct | new 构造 | new Ctor() |
getPrototypeOf | 获取原型 | Object.getPrototypeOf() |
setPrototypeOf | 设置原型 | Object.setPrototypeOf() |
isExtensible | 检查可扩展性 | Object.isExtensible() |
preventExtensions | 阻止扩展 | Object.preventExtensions() |
getOwnPropertyDescriptor | 获取属性描述符 | Object.getOwnPropertyDescriptor() |
defineProperty | 定义属性 | Object.defineProperty() |
ownKeys | 获取所有键 | Object.keys() 等 |
2.4 基础示例
const data = { x: 1 }const proxy = new Proxy(data, {// 拦截读取操作get(target, prop, receiver) {console.log('读取:', prop)return target[prop]},// 拦截写入操作set(target, prop, value, receiver) {console.log('写入:', prop, '=', value)target[prop] = valuereturn true // 必须返回 boolean},// 拦截 in 操作has(target, prop) {console.log('检查:', prop)return prop in target}
})proxy.x // 读取: x → 1
proxy.y = 2 // 写入: y = 2
'x' in proxy // 检查: x → true
2.5 Proxy 的特点
优点
- 拦截粒度细且全面:覆盖对象交互的所有基本操作
- 透明性:对外看起来像普通对象
- 强大的表达力:可实现响应式、不可变数据、验证等
缺点
- 性能开销:每次操作都触发陷阱
- 调试复杂:调用栈更深,行为不直观
- 规范约束:必须遵守不变式(invariants)
- 兼容性:无法完整 polyfill
3. Reflect 深度解析
3.1 Reflect 是什么
Reflect 是内置对象(类似 Math、Object),提供 13 个静态方法,用于执行对象的元操作。
typeof Reflect // "object"
typeof Math // "object"
typeof Object // "function"
3.2 Reflect 的三大作用
1. 语法操作函数化
把运算符、关键字变成可传递、可组合的函数:
// 语法操作(不能当参数传递)
'name' in obj
delete obj.name
new MyClass()// Reflect 函数化
Reflect.has(obj, 'name')
Reflect.deleteProperty(obj, 'name')
Reflect.construct(MyClass, [])
2. Proxy 的标准转发
在陷阱中用它执行默认行为:
const proxy = new Proxy(obj, {get(target, prop, receiver) {console.log('拦截:', prop)// 执行默认操作return Reflect.get(target, prop, receiver)}
})
3. 正确处理细节
- 自动处理 getter/setter 的 this(通过 receiver)
- 返回值一致(如
set
返回 boolean) - 遵守语言不变式
3.3 Reflect 的 13 个方法分类
分类 1:属性访问(最常用)- 4 个
方法 | 对应语法 | 返回值 |
---|---|---|
Reflect.get(obj, prop, receiver) | obj[prop] | 属性值 |
Reflect.set(obj, prop, val, receiver) | obj[prop] = val | boolean |
Reflect.has(obj, prop) | prop in obj | boolean |
Reflect.deleteProperty(obj, prop) | delete obj[prop] | boolean |
const obj = { x: 1 }Reflect.get(obj, 'x') // 1
Reflect.set(obj, 'y', 2) // true
Reflect.has(obj, 'y') // true
Reflect.deleteProperty(obj, 'y') // true
分类 2:属性描述符(配置)- 3 个
方法 | 对应语法 | 返回值 |
---|---|---|
Reflect.getOwnPropertyDescriptor(obj, prop) | Object.getOwnPropertyDescriptor | 描述符对象/undefined |
Reflect.defineProperty(obj, prop, desc) | Object.defineProperty | boolean |
Reflect.ownKeys(obj) | Object.keys + Symbol | 数组 |
Reflect.defineProperty(obj, 'age', {value: 25,writable: false
}) // trueReflect.getOwnPropertyDescriptor(obj, 'age')
// { value: 25, writable: false, ... }Reflect.ownKeys(obj) // ['x', 'age']
分类 3:原型链(继承)- 2 个
方法 | 对应语法 | 返回值 |
---|---|---|
Reflect.getPrototypeOf(obj) | Object.getPrototypeOf | 对象/null |
Reflect.setPrototypeOf(obj, proto) | Object.setPrototypeOf | boolean |
const parent = { x: 1 }
const child = {}Reflect.setPrototypeOf(child, parent) // true
Reflect.getPrototypeOf(child) === parent // true
child.x // 1
分类 4:可扩展性(冻结/密封)- 2 个
方法 | 对应语法 | 返回值 |
---|---|---|
Reflect.isExtensible(obj) | Object.isExtensible | boolean |
Reflect.preventExtensions(obj) | Object.preventExtensions | boolean |
const obj = { x: 1 }Reflect.isExtensible(obj) // true
Reflect.preventExtensions(obj) // true
Reflect.isExtensible(obj) // falseobj.y = 2 // 静默失败(严格模式报错)
分类 5:函数调用(特殊)- 2 个
方法 | 对应语法 | 返回值 |
---|---|---|
Reflect.apply(fn, thisArg, args) | fn.apply(thisArg, args) | 函数返回值 |
Reflect.construct(Ctor, args, newTarget) | new Ctor(...args) | 实例对象 |
function greet(msg) {return `${msg}, ${this.name}`
}Reflect.apply(greet, { name: 'Alice' }, ['Hello'])
// "Hello, Alice"class Person {constructor(name) {this.name = name}
}const p = Reflect.construct(Person, ['Bob'])
p.name // "Bob"
3.4 Reflect vs Object 的区别
特性 | Reflect | Object |
---|---|---|
返回值 | defineProperty 返回 boolean | 返回对象本身 |
功能覆盖 | 包含语法操作(has、delete 等) | 主要是静态方法 |
设计目标 | 元编程、配合 Proxy | 对象操作工具集 |
语义清晰 | 统一的返回值约定 | 不统一 |
// Object.defineProperty 返回对象
Object.defineProperty(obj, 'x', { value: 1 }) // 返回 obj// Reflect.defineProperty 返回 boolean
Reflect.defineProperty(obj, 'x', { value: 1 }) // true/false// 在 Proxy 陷阱中更合适
const proxy = new Proxy(obj, {defineProperty(target, prop, desc) {const success = Reflect.defineProperty(target, prop, desc)if (success) console.log('定义成功')return success // 必须返回 boolean}
})
3.5 为什么很少直接看到 Reflect 代码?
答案:Reflect 是底层 API,隐藏在框架内部。
普通业务代码不需要
// ❌ 业务代码不需要这样写
const name = Reflect.get(user, 'name')// ✅ 直接用语法更简洁
const name = user.name
框架内部大量使用
// Vue 3 源码(简化版)
function reactive(target) {return new Proxy(target, {get(target, key, receiver) {track(target, key)return Reflect.get(target, key, receiver) // ← 这里用了},set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver) // ← 这里用了trigger(target, key)return result}})
}
你用 Vue/MobX/Immer 时,就是间接在用 Reflect。
4. receiver 参数详解
4.1 receiver 是什么
receiver 是 Proxy 陷阱的第三个形参,由 JavaScript 引擎自动传入,表示"最初被访问的对象"(通常是 proxy 本身)。
const obj = { x: 1 }
const proxy = new Proxy(obj, {get(target, prop, receiver) {// ^^^^^^ ^^^^ ^^^^^^^^// 形参1 形参2 形参3console.log('target === obj:', target === obj) // trueconsole.log('receiver === proxy:', receiver === proxy) // truereturn target[prop]}
})proxy.x
4.2 receiver 的作用
确保 getter/setter 里的 this 指向 proxy(而不是 target),这样嵌套的属性访问才能继续被拦截。
问题场景
const obj = {_value: 100,get value() {return this._value // ← this 应该指向 proxy}
}// ❌ 不传 receiver - 拦截失效
const badProxy = new Proxy(obj, {get(target, prop) {if (prop === '_value') {return 999 // 想拦截}return target[prop] // getter 的 this 是 target,拦截失效}
})badProxy.value // 100(不是 999)⚠️
解决方案
// ✅ 传 receiver - 拦截生效
const goodProxy = new Proxy(obj, {get(target, prop, receiver) {if (prop === '_value') {return 999}return Reflect.get(target, prop, receiver) // 传 receiver}
})goodProxy.value // 999 ✅
4.3 执行流程详解
不传 receiver 的流程
proxy.value↓
陷阱:prop = 'value',返回 target['value']↓
相当于:obj.value(触发 getter)↓
getter 执行,this = obj↓
this._value → obj._value(绕过代理!)↓
返回:100
传 receiver 的流程
proxy.value↓
陷阱:prop = 'value',返回 Reflect.get(target, 'value', receiver)↓
Reflect 内部:getter.call(receiver)↓
getter 执行,this = receiver = proxy↓
this._value → proxy._value(触发代理!)↓
陷阱:prop = '_value',返回 999↓
返回:999
4.4 Reflect.get 内部原理
// Reflect.get 的简化实现
function reflectGet(target, prop, receiver) {// 1. 查找属性(可能在原型链上)let current = targetwhile (current) {const desc = Object.getOwnPropertyDescriptor(current, prop)if (desc) {// 2. 如果是 getterif (desc.get) {// ⭐ 关键:用 receiver 作为 this 调用 getterreturn desc.get.call(receiver)}// 3. 如果是普通属性return desc.value}// 4. 沿着原型链查找current = Object.getPrototypeOf(current)}return undefined
}
核心:Reflect.get
会用 receiver
作为 this
来调用 getter,就是普通的 .call(receiver)
。
4.5 完整运行示例
const obj = {_value: 100,get value() {console.log('getter 执行,this 是:', this)return this._value}
}const proxy = new Proxy(obj, {get(target, prop, receiver) {console.log(`\n陷阱拦截: ${prop}`)if (prop === '_value') {console.log('拦截 _value,返回 999')return 999}console.log('用 Reflect 转发')return Reflect.get(target, prop, receiver)}
})console.log('=== 访问 proxy.value ===')
const result = proxy.value
console.log('\n最终结果:', result)
输出:
=== 访问 proxy.value ===陷阱拦截: value
用 Reflect 转发
getter 执行,this 是: Proxy { ... }
陷阱拦截: _value
拦截 _value,返回 999最终结果: 999
4.6 receiver 的其他用途
检查访问来源
const obj = { x: 1 }
const proxy = new Proxy(obj, {get(target, prop, receiver) {if (receiver === proxy) {console.log('通过 proxy 访问')} else {console.log('通过其他对象访问(可能是继承)')}return target[prop]}
})proxy.x // 通过 proxy 访问const child = Object.create(proxy)
child.x // 通过其他对象访问(receiver 是 child)
存储元数据
const accessCount = new WeakMap()const proxy = new Proxy(obj, {get(target, prop, receiver) {// 在 receiver 上记录访问次数if (!accessCount.has(receiver)) {accessCount.set(receiver, {})}const counts = accessCount.get(receiver)counts[prop] = (counts[prop] || 0) + 1return Reflect.get(target, prop, receiver)}
})proxy.value
proxy.value
proxy.valueaccessCount.get(proxy) // { value: 3 }
5. getter/setter 与属性描述符
5.1 属性的两种类型
JavaScript 对象的属性分为两种:
类型 | 说明 | 描述符包含 |
---|---|---|
数据属性(Data Property) | 直接存储值 | value 、writable |
访问器属性(Accessor Property) | 通过函数访问 | get 、set |
5.2 数据属性
const obj = { x: 1 }Object.getOwnPropertyDescriptor(obj, 'x')
// {
// value: 1, ← 属性的值
// writable: true, ← 可以修改
// enumerable: true, ← 可以被遍历
// configurable: true ← 可以删除、修改描述符
// }
5.3 访问器属性(getter/setter)
什么是 getter?
getter = “获取器”(Get + er),伪装成属性的函数。
const obj = {_value: 100,// getter - 读取时调用get value() {console.log('getter 执行')return this._value},// setter - 写入时调用set value(v) {console.log('setter 执行')this._value = v}
}obj.value // 触发 getter → 100
obj.value = 50 // 触发 setter
为什么叫 getter?
英语中,动词 + er = 做这个动作的东西:
- work + er = worker(工作者)
- teach + er = teacher(教师)
- get + ter = getter(获取器)
- set + ter = setter(设置器)
getter 的描述符
const obj = {get value() {return 100}
}Object.getOwnPropertyDescriptor(obj, 'value')
// {
// get: [Function: get value], ← getter 函数
// set: undefined, ← 没有 setter
// enumerable: true,
// configurable: true
// }
5.4 普通方法 vs getter
const obj = {// 1. 普通属性x: 100,// 2. 普通方法getX() {return this.x},// 3. getter(访问器属性)get y() {return this.x * 2}
}// 访问方式:
obj.x // 100(读属性)
obj.getX() // 100(调用方法,必须加括号)
obj.y // 200(读 getter,不能加括号)
5.5 getter 必须有属性名
// ❌ 错误:这不是 getter,是普通方法
const obj1 = {get() {return 100}
}
obj1.get() // 100(必须加括号)// ✅ 正确:getter 有属性名
const obj2 = {get value() {return 100}
}
obj2.value // 100(不能加括号)
5.6 Object.defineProperty 定义 getter
const obj = {}Object.defineProperty(obj, 'value', {get() {// ← getter 函数return 100},set(v) {// ← setter 函数console.log('设置为', v)},enumerable: true,configurable: true
})obj.value // 100
obj.value = 50 // 设置为 50
5.7 getter 的典型用法
1. 私有属性封装
const user = {_age: 25, // 私有数据(约定用 _ 开头)get age() {return this._age},set age(v) {if (v < 0) throw Error('年龄不能为负')this._age = v}
}user.age // 25
user.age = -5 // 报错
2. 计算属性
const person = {firstName: 'Alice',lastName: 'Wang',get fullName() {return `${this.firstName} ${this.lastName}`}
}person.fullName // "Alice Wang"
3. 只读属性
const obj = {_secret: 'password',// 只有 getter,没有 setter = 只读get secret() {return this._secret}
}obj.secret // "password"
obj.secret = 'x' // 静默失败(严格模式报错)
5.8 getter 与 Proxy 的配合
const user = {_age: 25,// 对象的 getterget age() {return this._age}
}const proxy = new Proxy(user, {// Proxy 的 get 陷阱get(target, prop, receiver) {if (prop === '_age') {return 30 // 拦截私有属性}return Reflect.get(target, prop, receiver)}
})proxy.age // 30(getter 里的 this._age 访问了 proxy._age)
6. this 绑定机制
6.1 Proxy 陷阱中的 this
const handler = {count: 0,// ✅ 普通函数 - this 指向 handlerget(target, prop, receiver) {this.count++ // this = handler(对象方法的默认行为)console.log('访问次数:', this.count)return target[prop]}
}const proxy = new Proxy({}, handler)
proxy.x // 访问次数: 1
proxy.y // 访问次数: 2
关键:this 指向 handler 是因为"普通函数",不是因为 receiver。
6.2 箭头函数的问题
const handler = {count: 0,// ❌ 箭头函数 - this 不指向 handlerget: (target, prop, receiver) => {this.count++ // ❌ this 是外层作用域的console.log('访问次数:', this.count)return target[prop]}
}const proxy = new Proxy({}, handler)
proxy.x // 报错或 NaN
6.3 何时箭头函数可以用
如果陷阱里不需要访问 handler 的属性,箭头函数也可以:
const proxy = new Proxy(obj, {get: (target, prop, receiver) => {return target[prop] // ✅ 没问题,因为不需要 this}
})
6.4 receiver 与 this 的关系
receiver 是形参,和 this 无关:
const handler = {get(target, prop, receiver) {console.log('this === handler:', this === handler) // trueconsole.log('receiver === proxy:', receiver === proxy) // true// this 和 receiver 是两个完全不同的东西}
}
- this:指向 handler(普通函数的对象方法行为)
- receiver:引擎传入的参数(最初被访问的对象)
7. 实战案例与最佳实践
7.1 响应式数据(Vue 3 风格)
const subscribers = new Map()function reactive(obj) {return new Proxy(obj, {get(target, prop, receiver) {// 收集依赖track(target, prop)return Reflect.get(target, prop, receiver)},set(target, prop, value, receiver) {const result = Reflect.set(target, prop, value, receiver)// 触发更新trigger(target, prop)return result}})
}function track(target, prop) {if (currentEffect) {if (!subscribers.has(target)) {subscribers.set(target, new Map())}const depsMap = subscribers.get(target)if (!depsMap.has(prop)) {depsMap.set(prop, new Set())}depsMap.get(prop).add(currentEffect)}
}function trigger(target, prop) {const depsMap = subscribers.get(target)if (depsMap) {const effects = depsMap.get(prop)if (effects) {effects.forEach((effect) => effect())}}
}// 使用
let currentEffect = nullconst state = reactive({ count: 0 })function watchEffect(fn) {currentEffect = fnfn()currentEffect = null
}watchEffect(() => {console.log('count 变了:', state.count)
})state.count++ // count 变了: 1
state.count++ // count 变了: 2
7.2 数据验证
function createValidator(schema) {return new Proxy({},{set(target, prop, value, receiver) {const expectedType = schema[prop]if (expectedType && typeof value !== expectedType) {throw TypeError(`${prop} 必须是 ${expectedType} 类型`)}return Reflect.set(target, prop, value, receiver)}})
}const user = createValidator({name: 'string',age: 'number'
})user.name = 'Alice' // ✅
user.age = 25 // ✅
user.age = 'abc' // ❌ 报错:age 必须是 number 类型
7.3 自动日志
function createLogger(obj, objName = 'obj') {return new Proxy(obj, {get(target, prop, receiver) {console.log(`[读取] ${objName}.${String(prop)}`)return Reflect.get(target, prop, receiver)},set(target, prop, value, receiver) {console.log(`[写入] ${objName}.${String(prop)} = ${value}`)return Reflect.set(target, prop, value, receiver)}})
}const user = createLogger({ name: 'Alice' }, 'user')user.name // [读取] user.name
user.age = 25 // [写入] user.age = 25
7.4 函数缓存(Memoization)
function memoize(fn) {const cache = new Map()return new Proxy(fn, {apply(target, thisArg, args) {const key = JSON.stringify(args)if (cache.has(key)) {console.log('从缓存返回')return cache.get(key)}console.log('执行函数')const result = Reflect.apply(target, thisArg, args)cache.set(key, result)return result}})
}function fibonacci(n) {if (n <= 1) return nreturn fibonacci(n - 1) + fibonacci(n - 2)
}const cachedFib = memoize(fibonacci)cachedFib(5) // 执行函数 → 5
cachedFib(5) // 从缓存返回 → 5
7.5 负索引数组(Python 风格)
function createNegativeIndexArray(arr) {return new Proxy(arr, {get(target, prop, receiver) {if (typeof prop === 'string' && /^-\d+$/.test(prop)) {const index = target.length + Number(prop)return target[index]}return Reflect.get(target, prop, receiver)}})
}const arr = createNegativeIndexArray([10, 20, 30])arr[0] // 10
arr[-1] // 30(最后一个)
arr[-2] // 20(倒数第二个)
arr.push(40) // ✅ 数组方法也正常
7.6 只读对象(白名单可写)
function createReadonly(obj, whitelist = []) {return new Proxy(obj, {set(target, prop, value, receiver) {if (!whitelist.includes(prop)) {console.warn(`属性 ${String(prop)} 是只读的`)return false}return Reflect.set(target, prop, value, receiver)}})
}const config = createReadonly({ port: 3000, host: 'localhost' },['port'] // 只有 port 可以修改
)config.port = 8080 // ✅
config.host = '0.0.0.0' // ⚠️ 只读,不生效
7.7 最佳实践总结
1. 透明优先
// ✅ 好的做法:非必要不改变默认语义
const proxy = new Proxy(obj, {get(target, prop, receiver) {if (prop === 'secret') {return '***' // 只拦截特定属性}return Reflect.get(target, prop, receiver) // 其他正常}
})// ❌ 坏的做法:过度拦截
const proxy2 = new Proxy(obj, {get(target, prop, receiver) {// 对所有属性都做复杂处理return someComplexLogic(target, prop)}
})
2. 只拦截底层数据
const user = {_age: 25,get age() {return this._age},get info() {return `年龄: ${this.age}`}
}const proxy = new Proxy(user, {get(target, prop, receiver) {// 只拦截底层数据if (prop === '_age') {return 30}// age 和 info 的 getter 自动工作return Reflect.get(target, prop, receiver)}
})
3. 返回值一致性
// ✅ set/defineProperty 等必须返回 boolean
const proxy = new Proxy(obj, {set(target, prop, value, receiver) {const success = Reflect.set(target, prop, value, receiver)if (!success) {console.error('设置失败')}return success // 必须返回}
})
4. 清理资源
// 使用可撤销代理
const { proxy, revoke } = Proxy.revocable(obj, handler)// 使用完后撤销
revoke()
proxy.x // ❌ 报错:代理已撤销
8. 常见误区与问题
8.1 误区 1:不用 receiver 就是错误
纠正:不是错误,而是有局限性。
// 简单场景(只拦截普通属性)- 不用 receiver 没问题
const obj = { x: 1, y: 2 }
const proxy = new Proxy(obj, {get(target, prop) {if (prop === 'x') return 999return target[prop] // ✅ OK}
})// 复杂场景(有 getter 嵌套)- 不用 receiver 会失效
const obj2 = {_value: 100,get value() {return this._value}
}
const proxy2 = new Proxy(obj2, {get(target, prop) {if (prop === '_value') return 999return target[prop] // ⚠️ getter 的 this 是 target,拦截失效}
})
proxy2.value // 100(不是 999)
结论:
- 简单拦截:可以不用 receiver
- 有 getter/setter:必须用 receiver
8.2 误区 2:Reflect 都返回 boolean
纠正:返回值类型各不相同。
方法 | 返回值 |
---|---|
Reflect.get | 属性值(任意类型) |
Reflect.set | boolean |
Reflect.has | boolean |
Reflect.ownKeys | 数组 |
Reflect.apply | 函数返回值 |
8.3 误区 3:receiver 会自动改变 this
纠正:receiver 只是形参,Reflect 用它来 call getter。
// this 的绑定不是自动的,是 Reflect.get 内部做的
function reflectGet(target, prop, receiver) {const desc = Object.getOwnPropertyDescriptor(target, prop)if (desc && desc.get) {// 手动用 receiver 作为 this 调用return desc.get.call(receiver)}return desc.value
}
8.4 误区 4:Proxy 陷阱可以指定属性名
纠正:陷阱是全局的,用 prop 参数判断。
// ❌ 错误:没有这种写法
const proxy = new Proxy(obj, {get value() {// 语法错误return 100}
})// ✅ 正确:统一的 get 陷阱
const proxy = new Proxy(obj, {get(target, prop, receiver) {if (prop === 'value') {return 100}return Reflect.get(target, prop, receiver)}
})
8.5 误区 5:getter 不需要属性名
纠正:getter 必须有属性名。
// ❌ 这不是 getter,是普通方法
const obj1 = {get() {return 100}
}
obj1.get() // 必须加括号// ✅ 这是 getter
const obj2 = {get value() {return 100}
}
obj2.value // 不能加括号
8.6 性能注意事项
// ❌ 坏做法:在热路径中频繁触发代理
for (let i = 0; i < 1000000; i++) {proxy.value // 每次都触发陷阱
}// ✅ 好做法:缓存常用值
const value = proxy.value
for (let i = 0; i < 1000000; i++) {doSomething(value)
}
8.7 调试技巧
// 添加调试日志
const proxy = new Proxy(obj, {get(target, prop, receiver) {console.log('[DEBUG] get:', prop, '| this:', this, '| receiver:', receiver)return Reflect.get(target, prop, receiver)}
})// 使用 Proxy.revocable 方便调试
const { proxy, revoke } = Proxy.revocable(obj, handler)
// 调试完后撤销
revoke()
9. 技术选型建议
9.1 何时使用 Proxy
场景 | 适用性 | 替代方案 |
---|---|---|
响应式系统 | ✅ 最佳 | Vue 2 的 defineProperty(有限制) |
数据验证 | ✅ 推荐 | 手动校验函数 |
日志/调试 | ✅ 推荐 | 手动包装 |
安全沙箱 | ✅ 推荐 | Object.freeze(有限制) |
性能敏感场景 | ❌ 不推荐 | 直接访问 |
9.2 Proxy vs defineProperty
特性 | Proxy | defineProperty |
---|---|---|
覆盖范围 | 所有操作 | 只有 get/set |
新增属性 | ✅ 自动拦截 | ❌ 需要重新定义 |
数组索引 | ✅ 支持 | ❌ 不支持 |
性能 | ⚠️ 稍慢 | ✅ 较快 |
兼容性 | ⚠️ 无法 polyfill | ✅ IE9+ |
建议:
- 新项目:优先 Proxy
- 需要兼容旧浏览器:defineProperty
- 性能关键:直接访问或 defineProperty
9.3 与其他状态管理的对比
方案 | 原理 | 适用场景 |
---|---|---|
Proxy(Vue 3/MobX) | 可变 + 自动追踪 | UI 框架、复杂状态 |
Immer | Proxy + 不可变 | Redux/Zustand reducer |
Zustand | 订阅模式 + 不可变 | React 轻量状态 |
Redux | 纯函数 + 不可变 | 大型应用、时间旅行 |
10. 总结
10.1 核心概念速查
概念 | 本质 | 作用 |
---|---|---|
Proxy | 代理对象 | 拦截对象操作 |
陷阱(Trap) | handler 的方法 | 具体的拦截逻辑 |
Reflect | 内置对象 | 执行标准元操作 |
receiver | 陷阱的形参 | 最初被访问的对象 |
getter | 访问器属性 | 伪装成属性的函数 |
元编程 | 操控代码行为 | 改变语言能力 |
10.2 最佳实践口诀
- Proxy 拦截,Reflect 转发
- 有 receiver 参数的陷阱,必须传给 Reflect
- 返回 boolean 的 Reflect,在陷阱中也要返回 boolean
- 只拦截底层数据,让 getter 自动工作
- 透明优先,非必要不改变默认语义
10.3 记忆图谱
元编程
├── Proxy(拦截层)
│ ├── 13 种陷阱
│ │ ├── get/set(属性访问)
│ │ ├── has/deleteProperty(运算符)
│ │ ├── apply/construct(函数)
│ │ └── ...
│ └── receiver(传递 this)
│
└── Reflect(执行层)├── 13 个方法(一一对应)├── 语法操作函数化└── 正确处理细节(getter、原型链)
10.4 学习路径建议
- 基础阶段:理解 Proxy 基本用法和常用陷阱(get/set)
- 进阶阶段:掌握 Reflect 的作用和 receiver 的意义
- 高级阶段:实现响应式系统、深入理解 this 绑定
- 实战阶段:阅读 Vue 3、MobX、Immer 源码
10.5 常见问题速查
问题 | 答案 |
---|---|
为什么要用 Reflect? | 在 Proxy 陷阱中执行标准操作 |
receiver 是什么? | 陷阱的形参,表示最初被访问的对象 |
为什么要传 receiver? | 确保 getter 的 this 指向 proxy |
getter 和陷阱的区别? | getter 是对象属性,陷阱是 Proxy 方法 |
箭头函数能用吗? | 可以,但不能访问 handler 的 this |
参考资源
官方文档
- MDN - Proxy
- MDN - Reflect
- ECMAScript Specification - Proxy
开源项目
- Vue 3 - Reactivity
- MobX
- Immer
- Valtio
相关文章
- Vue 3 Deep Dive: Proxy-based Reactivity
- JavaScript Meta Programming
文档版本:v1.0
最后更新:2025-10-07
适用范围:ES2015+ 环境