proxy 是一种在目标对象与外界之间插入可控拦截逻辑的代理机制,通过 handler 中的 trap(如 get/set)拦截操作,需配合 Reflect 方法确保原型链行为正确,常用于响应式、权限控制等场景。

Proxy 是什么?它怎么拦截对象操作?
Proxy 不是装饰器,也不是继承机制,它是一个“代理层”——在目标对象和外界之间插入一层可控的拦截逻辑。Proxy 实例本身不持有数据,所有读写最终都流向你指定的 target,但中间可以被 handler 拦截、改写、拒绝或记录。
常见错误是以为 new Proxy(obj, {}) 就能自动响应式,其实空 handler 什么都不做;必须显式定义如 get、set 等 trap 才生效。
使用场景包括:实现响应式(vue 3)、日志追踪、权限控制、不可变封装、验证代理、Mock 数据等。
关键点:
立即学习“Java免费学习笔记(深入)”;
-
handler中的每个方法(如get)接收至少两个参数:target和key(或Property),第三个通常是receiver(常为proxy自身) - 不要直接在
get里返回target[key],而应调用Reflect.get(target, key, receiver)—— 否则会丢失原型链上的访问行为(比如hasOwnProperty或 getter) -
set必须返回布尔值,true表示赋值成功,false且严格模式下会抛TypeError
Reflect 是什么?为什么不能直接用 target[key]?
Reflect 不是类,也不是构造函数,它是一组静态方法集合,每项都与 Proxy 的 trap 一一对应(如 Reflect.get ↔ get trap),作用是把原本隐式、分散、容易出错的对象操作,统一为可编程、可拦截、可重写的函数调用。
直接写 target[key] 看似简单,但在 Proxy 中会跳过原型链上的 getter、忽略 receiver 绑定、无法被其他拦截逻辑复用。而 Reflect.get(target, key, receiver) 显式传递上下文,确保行为一致。
常见误用:
- 在
gettrap 中写return target[key]→ 原型上定义的get不会被触发 - 在
settrap 中写target[key] = value→ 绕过 setter、忽略receiver、无法捕获失败 - 用
Object.keys(target)替代Reflect.ownKeys(target)→ 前者不包含不可枚举属性,后者忠于实际自有属性列表
一个最小可用的响应式代理示例
下面这个例子展示如何用 Proxy + Reflect 实现基础响应式:每次读取时收集依赖,每次设置时触发更新(简化版,无依赖管理细节):
const handlers = { get(target, key, receiver) { console.log(`[GET] ${String(key)}`); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log(`[SET] ${String(key)} =`, value); return Reflect.set(target, key, value, receiver); } }; const data = { count: 0 }; const proxy = new Proxy(data, handlers); proxy.count++; // 输出 [GET] count → [SET] count = 1
注意:proxy.count++ 是先 get 再 set,所以你会看到两行日志。如果只监听 set,就漏掉了读取依赖关系——这也是 Vue 3 为何需要配合 track/trigger 机制的原因。
容易被忽略的陷阱:receiver、this 绑定与循环代理
最常踩的坑不是语法,而是语义。例如:
-
receiver参数必须传给Reflect方法,否则this在 getter 中指向target而非proxy,导致无法触发嵌套代理或拦截失效 - 对数组或函数对象做
Proxy时,某些操作(如Array.prototype.push)会改变Length,但若没在set中处理length变更,就可能漏掉响应 - 递归代理(如深响应式)必须避免无限代理:对已代理过的对象缓存并复用,否则
proxy.proxy.proxy...会爆栈 -
in操作符走hastrap,for...in走ownKeys+getOwnPropertyDescriptor,二者行为不同,不能只实现一个
复杂点不在 API 多少,而在每种 js 操作背后对应的 trap 是否覆盖完整。一个看似简单的“监听对象变化”,实际要对至少 13 个 trap 做合理实现才能健壮。