Proxy 是 JavaScript 唯一能拦截对象基本操作的底层机制,用于拦截 in、for…in、delete、函数调用、默认属性访问等 Object.defineProperty 无法处理的操作,但仅作用于自身属性且不代理原型链。

Proxy 不是语法糖,也不是替代 Object.defineProperty 的“升级版”——它是 JavaScript 唯一能拦截对象基本操作(如读、写、枚举、构造)的底层机制。
什么时候必须用 Proxy,而不是 Object.defineProperty
当你需要拦截以下操作时,Object.defineProperty 无能为力,只能选 Proxy:
-
in操作符(key in obj)——需拦截hastrap - for…in /
Object.keys()/Reflect.ownKeys()——需ownKeystrap - 删除属性:
delete obj.key——需deletePropertytrap - 函数调用:
obj()——需apply或construct(如果目标是函数) - 访问不存在的属性不报错,而是返回默认值(类似 Python 的
__getattr__)——靠gettrap +Reflect.get回退
注意:Proxy 无法代理普通对象的原型链变更(比如改 obj.__proto__),也不拦截对原型上属性的访问——它只作用于被代理对象自身。
get 和 set trap 的常见误用点
很多人以为 get/set 和 Object.defineProperty 的 getter/setter 一样,但行为差异极大:
立即学习“Java免费学习笔记(深入)”;
-
settrap 中,赋值失败不会自动抛错;必须显式return true表示成功,否则静默失败(严格模式下会报TypeError) -
gettrap 中,若目标对象不可配置/不可写,又试图返回不同值,可能触发意外异常 - 不要在
get里直接返回新对象(如return {}),这会破坏引用一致性;应缓存或用Reflect.get(target, prop, receiver)委托原逻辑 - 避免在
get中递归调用target[prop],否则无限循环(正确做法是用Reflect.get)
示例:安全的默认值访问
const withDefault = (target, defaultValue = null) =>
new Proxy(target, {
get(obj, prop) {
return prop in obj ? Reflect.get(obj, prop) : defaultValue;
}
});
const user = withDefault({ name: 'Alice' }, 'N/A');
console.log(user.age); // 'N/A'
Proxy 不能代理什么?哪些场景它会失效
Proxy 是浅代理,且有明确边界:
- 只代理第一层属性,嵌套对象不会自动被代理(需在
get中按需递归 wrap) - 代理数组时,
.push()、.pop()等方法仍走原生逻辑,不会触发set;要拦截得重写方法或监听length和索引变化 - 代理后对象与原对象不相等:
proxy === original为false,且Object.is(proxy, original)也是false - 某些内置对象(如
document、Window)无法被代理,会抛TypeError: Cannot create proxy with a non-object as target -
Proxy无法拦截instanceof、typeof、Object.prototype.toString.call()等间接操作,除非配合getPrototypeOf/isExtensible等 trap 配合处理
真正难的不是写一个 Proxy,而是判断该不该用、在哪一层做代理、是否要递归、以及如何让开发者和工具链(比如 Vue 响应式、React DevTools)正确识别它的行为。漏掉 ownKeys 或写错 has,就可能让 for…in 或 in 判断失效——这种问题往往只在特定数据结构下暴露,调试成本很高。











