
JavaScript 原型链本身不支持“动态拦截”——它只是属性查找的路径机制,不是可编程的钩子。真正实现属性动态拦截与代理,靠的是 Proxy,而不是修改或遍历原型链。但 Proxy 可以和原型链协同工作:只要目标对象能通过原型链访问到属性(比如方法在 Array.prototype 上),Proxy 的 get 拦截器仍会触发,因为访问行为是从代理对象发起的。
用 Proxy 拦截原型链上的属性读取
当访问一个对象上不存在的属性时,JS 会沿原型链向上查找;而 Proxy 的 get 拦截器会在每次属性访问时被调用——无论该属性在对象自身还是原型上。关键在于:拦截发生在“访问入口”,不是在原型内部。
- 必须在
get中使用Reflect.get(target, prop, receiver),其中receiver是 proxy 实例,确保 getter 或原型方法中的this正确指向代理对象 - 如果只写
target[prop],会跳过原型链和 getter,导致行为不一致 - 示例:拦截所有属性访问,包括继承来的
toString
Object.setPrototypeOf(obj, { b: 2 }); // b 在原型上
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log('访问:', prop);
return Reflect.get(target, prop, receiver); // ✅ 正确触发原型查找
}
});
proxy.b; // 输出 '访问: b',返回 2
拦截原型方法调用(如 push、map)
内置类型(如 Array、Date)的方法定义在原型上,但调用时仍是“通过实例发起”。Proxy 的 get 拦截器拿到函数后,可返回包装函数,在执行前后插入逻辑。
- 不要直接调用原函数(如
value()),否则绕过拦截;要用value.apply(this, args)或Reflect.apply(value, this, args) - 注意 this 绑定:包装函数中
this应指向代理对象,而非原始 target - 对数组等对象,需额外处理 length、索引赋值等行为(Proxy 不自动代理数组变异方法如
push的内部逻辑)
const proxyArr = new Proxy(arr, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
return function(...args) {
console.log(`调用 ${prop},参数:`, args);
const result = Reflect.apply(value, receiver, args);
console.log(`${prop} 返回:`, result);
return result;
};
}
return value;
}
});
proxyArr.push(3); // 触发日志,且正确修改原数组
为什么不能直接“拦截原型链”?
原型链是 JS 引擎内部的查找机制,没有暴露可注册的回调接口。你无法给 Object.prototype 或 Array.prototype 添加“当某个属性被查找到时通知我”的钩子。所谓“原型链拦截”,本质是:
- 拦截对象的属性访问行为(用 Proxy)
- 依赖该访问最终落到原型上(即属性不在自身,但在原型)
- 不修改原型,也不污染全局,完全运行时可控
试图重写原型方法(如 Array.prototype.push = wrap(...))属于静态覆盖,不是动态代理,且有全局副作用、无法按实例区分逻辑、易引发递归等问题。
补充:Proxy 对原型链的限制
Proxy 能拦截大多数通过代理对象发起的操作,但以下情况不会触发 get:
- 私有字段
#x—— 语法层面禁止代理,任何 Proxy 都无法拦截 -
Object.getOwnPropertyDescriptor(proxy, key)—— 走getOwnPropertyDescriptortrap,不是get -
for...in和Object.keys()—— 受ownKeys和getOwnPropertyDescriptor控制,不触发get - 直接访问原型对象(如
obj.__proto__.x)—— 访问的是另一个对象,Proxy 不介入
所以,“动态拦截”始终围绕代理对象展开,原型链只是它背后自然生效的查找机制,不是被直接操作的目标。











