JavaScript 中的 Proxy 是一把极其强大的API,允许你劫持对象的读取、赋值、删除、函数调用等等。但它并不是“无所不能”。在某些边界场景下,Proxy 是拦截不了的,甚至有些你以为能拦的操作,背后根本就没调用 proxy。
本篇将系统梳理 Proxy 中所有 不能拦截 的行为,配合 Reflect 机制一同解析,避免在项目中对 Proxy 抱有不切实际的幻想。
Proxy 可以拦截的操作(简略复习)
先来看 Proxy 到底可以劫持哪些操作。根据 MDN 的定义,常见的 trap 包括:
get:属性读取set:属性赋值has:in 操作符deleteProperty:delete 操作ownKeys:Object.keys() / Reflect.ownKeys()getPrototypeOf/setPrototypeOf:原型访问与设置apply/construct:函数调用与 new 操作
但接下来我们重点讲不能劫持的。
❌ Proxy 无法拦截的行为
1. typeof proxy
const p = new Proxy(function () {}, {})
console.log(typeof p) // 'function'
无论你怎么设置 trap,typeof 操作始终不会触发任何 trap(包括 apply 或 construct),这是语言层面的保留行为。
typeof是静态操作,不触发运行时 hook。
2. 原始值转换:String(proxy)、Number(proxy)、+proxy
const p = new Proxy({
toString() {
return 'proxied'
}
}, {
get(target, key) {
console.log('get', key)
return target[key]
}
})
console.log(String(p)) // 'proxied'
虽然你可能能拦住 toString 的访问,但你无法拦住整个“类型强转行为”。像 +proxy、Number(proxy)、== null 等比较运算,底层通过 ToPrimitive 转换,不触发 trap。
除非你拦截的是一个函数调用的属性访问阶段,否则基本无解。
3. instanceof 操作符(如果目标不是构造函数)
const target = {}
const proxy = new Proxy(target, {})
console.log(proxy instanceof Object) // true
因为 instanceof 是基于 proxy.[[Prototype]] 来查找构造链的,除非你修改其 getPrototypeOf,否则也无法改变。
更重要的是:
- 如果目标不是 callable,
instanceof也不会触发任何 trap。 - 要改变 instanceof 的行为,只能返回一个特殊的构造器对象。
4. with 作用域链内部绑定
const p = new Proxy({ secret: 42 }, {
has(t, k) {
console.log('has', k)
return true
}
})
with (p) {
console.log(secret) // 42,但不会触发 get!
}
使用 with 时,虽然触发了 has trap(用于作用域绑定判断),但属性读取是直接绑定原始对象的内部值,不走 get trap!
这是一个非常冷门的行为,通常只有你在 try-catch + sandbox 时才会踩到。
5. Object.prototype.toString.call(proxy) 的 [object X] 不可自定义
Object.prototype.toString.call(new Proxy({}, {}))
// [object Object] —— 无法改变
即便你设置了 Symbol.toStringTag,但 proxy 仍然受限于宿主对象类型。
const proxy = new Proxy({
[Symbol.toStringTag]: 'Custom'
}, {})
console.log(Object.prototype.toString.call(proxy)) // [object Object] 而不是 [object Custom]
6. JSON.stringify 无法被完全拦截
const proxy = new Proxy({
toJSON() {
return 'hello'
}
}, {
get(t, k) {
console.log('get', k)
return t[k]
}
})
console.log(JSON.stringify(proxy)) // "hello",触发了 toJSON,但无法完全控制字符串化
虽然你可以拦截 toJSON,但无法拦截 JSON.stringify 的整体行为逻辑。
🤖 Reflect 是干什么用的?为啥 trap 里都要写 Reflect?
在 Proxy 的 trap 中,正确的写法通常长这样:
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
}
而不是:
get(target, key) {
return target[key]
}
这是因为 Reflect 保留了原始对象操作的行为语义,确保原型链正确、上下文正确(特别是 setter / getter 中的 this)。
最常见的错误是:使用 target[key] 触发了意外的 getter / proxy 嵌套,导致递归。
所以,除非你明确要修改行为,否则建议所有 trap 都用 Reflect.xxx。
你还有什么操作你试过 Proxy ,但“没反应”?