proxy不能直接拦截方法调用,需通过get拦截属性读取并返回带apply的代理函数,再在apply中拦截实际调用,从而实现对方法调用的完整控制。

Proxy 本身不能直接拦截“方法调用”,它拦截的是属性访问(get)和函数执行(apply)这两个阶段。真正实现“拦截对象方法调用”的关键,是把方法获取和方法执行拆开处理:先用 get 拦截方法属性的读取,返回一个代理过的函数;再在这个函数上用 apply 拦截实际调用。
拦截 get 阶段:包装方法为可代理函数
当代码写 obj.method() 时,JS 先执行 obj.method(触发 get),再对结果调用 ()(触发 apply)。所以要在 get 中判断返回值是否为函数,如果是,就用另一个 Proxy 包裹它,让它支持 apply 拦截。
- 原对象的方法必须是普通函数(箭头函数、类方法等也适用,只要它是函数值)
-
get返回的不能是原始函数,而是一个带apply的 Proxy 实例 - 需保留原始 this 和参数,否则调用会出错
拦截 apply 阶段:在函数调用时介入
被 get 返回的代理函数,其 apply 可以捕获所有调用行为——包括谁调用(thisArg)、传了什么参数(args)、甚至决定是否执行原函数。
-
apply(target, thisArg, args)中的target是原始函数,可安全调用target.apply(thisArg, args) - 可以修改
args、提前返回、抛错、或异步延迟执行 - 注意:
thisArg是调用时绑定的上下文,不是原对象,需透传
完整示例:记录并校验方法调用
下面是一个能打印日志、检查参数类型的简单代理:
const obj = {
greet(name) {
return `Hello, ${name}`;
},
add(a, b) {
return a + b;
}
};
const handler = {
get(target, prop) {
const value = target[prop];
if (typeof value === 'function') {
return new Proxy(value, {
apply(target, thisArg, args) {
console.log(`[CALL] ${prop}(${args.join(', ')})`);
// 可加校验逻辑,例如 greet 必须传字符串
if (prop === 'greet' && typeof args[0] !== 'string') {
throw new TypeError('greet expects a string');
}
return target.apply(thisArg, args);
}
});
}
return value;
}
};
const proxy = new Proxy(obj, handler);
proxy.greet('Alice'); // 输出日志,返回 "Hello, Alice"
// proxy.greet(123); // 抛出 TypeError
注意事项与常见坑
这种模式看似简单,但有几个易忽略点:
- 不能拦截私有方法或 Symbol 属性上的函数(除非显式在
get中处理 Symbol 键) - 若方法内部用了
arguments或caller,代理后可能失效(现代代码应避免依赖这些) - 多次代理同一函数会嵌套 Proxy,性能下降;建议缓存已代理的函数
- class 实例方法、getter/setter 返回的函数,同样适用该模式
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











