Proxy可通过拦截apply陷阱监控函数调用,需代理包装函数而非原函数;核心是闭包保存目标函数并在apply中调用,以记录参数、返回值、耗时等审计信息。

JavaScript 中 Proxy 可以通过拦截 apply(或 construct)陷阱来实现对函数调用的监控,从而记录参数、返回值、执行时间等审计信息。关键在于:Proxy 不能直接代理普通函数对象本身(因为函数是不可扩展的),但可以代理一个**包装后的函数**——即把目标函数封装进一个可被 Proxy 拦截的函数中。
核心思路:用 Proxy 包裹函数调用行为
Proxy 的 apply 陷阱会在通过函数调用语法(fn(...args))执行时触发,适用于普通函数和箭头函数;construct 则用于 new Fn() 场景。要审计函数调用,重点使用 apply。
- 目标函数不能直接传给
new Proxy(target, handler)(会报错:Cannot create proxy with a non-object as target or handler) - 正确做法:将目标函数作为闭包变量保存,让 Proxy 的
apply内部调用它 - 这样既保留原函数的
this绑定、原型链、length 等特性,又能插入日志逻辑
基础审计代理实现(含参数与返回值记录)
以下是一个轻量、可复用的日志代理工厂:
function createAuditProxy(fn, options = {}) {
const { logger = console.log, includeTime = true } = options;
<p>return new Proxy(function (...args) {
// apply 陷阱在 fn(...args) 被调用时触发
}, {
apply(target, thisArg, args) {
const start = performance.now();
let result, error;</p><pre class="brush:php;toolbar:false;"> try {
result = fn.apply(thisArg, args);
} catch (e) {
error = e;
}
const duration = includeTime ? ` (+${(performance.now() - start).toFixed(2)}ms)` : '';
const logMsg = `[AUDIT] ${fn.name || 'anonymous'}(${args.map(a => JSON.stringify(a)).join(', ')})${duration}`;
if (error) {
logger('❌', logMsg, '→ threw:', error);
} else {
logger('✅', logMsg, '→ returns:', JSON.stringify(result));
}
if (error) throw error;
return result;
}}); }
使用示例:
立即学习“Java免费学习笔记(深入)”;
const add = (a, b) => a + b; const auditedAdd = createAuditProxy(add); <p>auditedAdd(2, 3); // ✅ [AUDIT] add(2, 3) (+0.05ms) → returns: "5"
增强功能:支持异步函数、自定义字段与过滤
真实场景中常需处理 Promise、忽略敏感参数(如密码)、添加上下文 ID 或调用栈:
- 判断
fn是否为 async 函数:检查fn.constructor.name === 'AsyncFunction' - 对 Promise 返回值,可在
then/catch中记录最终结果或错误 - 用
options.filterArgs接收一个索引数组或键名列表,跳过敏感字段序列化 - 用
options.context注入 traceId、用户ID等审计元数据
简化的异步支持片段:
apply(target, thisArg, args) {
const start = performance.now();
const callDesc = `${fn.name || 'anonymous'}(${args.slice(0, 3).map(JSON.stringify).join(', ')})`;
<p>const result = fn.apply(thisArg, args);</p><p>if (result instanceof Promise) {
return result
.then(val => {
logger('✅', <code>[ASYNC] ${callDesc}</code>, <code>→ resolved: ${JSON.stringify(val)}</code>);
return val;
})
.catch(err => {
logger('❌', <code>[ASYNC] ${callDesc}</code>, <code>→ rejected:</code>, err);
throw err;
});
}</p><p>// 同步逻辑(同上)
}注意事项与边界情况
Proxy 日志审计不是万能的,需注意以下限制:
-
无法拦截方法内联调用:如
obj.method()若未对obj.method单独代理,则不会触发 -
this 绑定需谨慎:若原函数依赖特定
this,确保调用时传入正确的thisArg(上面示例已用fn.apply(thisArg, args)正确传递) -
函数属性丢失:代理后函数的
name、length、toString()不再等于原函数(可通过get陷阱透传部分属性缓解) -
调试体验下降:堆栈中显示的是 Proxy 包装函数,而非原始函数名(可用
Object.defineProperty(proxy, 'name', { value: fn.name })改善)
不复杂但容易忽略。


















