Proxy 是对象的拦截代理层,创建新对象拦截对它的操作而非包装原对象;必须掌握 get/set/has/ownKeys 四个基础 trap,代理数组时需特别处理 length 和原型方法,且注意性能与 IE 兼容性问题。
Proxy 不是给对象加一层“壳”,而是创建一个新对象,所有对它的访问(读、写、枚举、构造等)都会被重定向到你定义的 handler 中处理。原始对象本身完全不受影响,Proxy 拦截的是对代理对象的操作行为,不是修改原对象的属性访问机制。
常见误解是把它当“响应式封装工具”直接用,结果发现 Object.defineProperty 那套逻辑不适用——因为 Proxy 拦截的是运行时行为,不是静态属性定义。
这四个 trap 覆盖了日常 90% 的拦截需求。漏掉 has 或 ownKeys,会导致 in 操作符、for...in、Object.keys() 等行为不符合预期,尤其在做数据校验或权限控制时容易出错。
get(target, prop, receiver):拦截属性读取。注意必须返回值,否则为 undefined;若要保持原行为,需显式 return Reflect.get(...arguments)
set(target, prop, value, receiver):拦截赋值。必须返回布尔值(true 表示成功),否则会抛 TypeError
has(target, prop):拦截 prop in proxy。默认不触发,必须手动实现,否则始终返回 true(因为 Proxy 默认透传)ownKeys(target):拦截 Object.getOwnPropertyNames() 和 Reflect.ownKeys()。若目标对象有 Symbol 属性或不可枚举属性,这里必须显式返回,否则会被过滤掉const target = { a: 1, b: 2 };
const handler = {
get(target, prop) {
console.log('get', prop);
return prop in target ? target[prop] : 'default';
},
set(target, prop, value) {
if (typeof value !== 'number') {
throw new TypeError('Only number allowed');
}
target[prop] = value;
return true;
},
has(target, prop) {
return prop !== 'secret' && prop in target;
},
ownKeys(target) {
return Object.keys(target);
}
};
const proxy = new Proxy(target, handler);
Proxy 可以代理数组,但 push、pop、length 修改等操作不会自动触发 set,因为它们本质是调用原型方法,而数组的 length 是一个有 setter 的 accessor 属性——但 Proxy 默认不拦截原型链上的操作,除非你在 set 中额外判断 prop === 'length' 或拦截 defineProperty。
更关键的是:proxy.push(1) 触发的是 proxy[proxy.length] = 1 + proxy.length++,后者会走 set,但前者若没实现 get 对 length 的响应,就可能读到旧值。
get 中处理 length 的读取逻辑push/splice 等方法,得在 handler 中重写对应方法,或用 apply trap 拦截函数调用Array.isArray(proxy) 判断类型——它返回 true,但某些库(如 Vue 2 的响应式)内部靠 __proto__ 或构造器识别,Proxy 会破坏该链每次属性访问都多一层函数调用,V8 虽已优化,但在循环体、渲染函数、高频事件回调中滥用 Proxy,会明显拖慢执行速度。尤其当 handler 里有复杂逻辑(如深克隆、正则匹配、网络请求)时,问题立刻暴露。
兼容性方面:Proxy 不支持 IE,且 Node.js Object.defineProperty + 递归代理(仅限已知属性)。
console.trace() 在 trap 里打点,确认是否被意外高频触发set 却忘了同步更新 length,或者重写了 get 却没透传 Symbol.iterator,结果 [
...proxy] 就崩了。