能否扩展JavaScript默认函数原型以在每次函数调用时执行自定义代码?
问题
现有如下函数:
function a(someparams){ console.log('a called') } function b(){ console.log('b called') } ... const c = (someParam) => { console.log('c called')}
我希望在页面代码最开头扩展默认函数原型,比如添加:
Function.prototype.onCall = (args) => {console.log('Proxy fn called!',args)}
让所有已有及新定义的函数在被调用时,都能打印Proxy fn called!,目的是自动校验参数有效性。目前遍历window对象中函数的方案仅对已有函数生效,我想通过扩展原型实现,且不考虑TypeScript或Flow方案。
请问这是否可行?应该从哪方面入手?
我找到了以下方案:
(function() { var call = Function.prototype.call; Function.prototype.call = function() { console.log(this, arguments); // 此处可执行自定义操作 return call.apply(this, arguments); }; }());
但它仅在显式调用.call或.apply时生效,普通调用如someFunction();不会触发。
另外我考虑过装饰器方案,但它尚未成为标准,不过有对应的Babel插件,请问该方案是否可行?还是我找错了方向?
解决方案
一、扩展原型实现全局函数调用拦截的可行性
可行,但要注意:普通函数调用(如fn())不会直接触发Function.prototype.call,而是由JS引擎直接执行函数内部代码,所以你之前修改call方法的方案只能覆盖显式调用call/apply的场景。要拦截所有函数调用,得从修改函数执行的入口逻辑入手。
二、正确的实现方向
1. 重写Function.prototype.constructor拦截新函数创建
通过拦截函数的构造过程,给所有新创建的函数包裹拦截逻辑,同时对已有函数批量包装:
// 保存原生Function构造器 const OriginalFunction = Function; // 重写Function构造器,拦截新函数创建 Function = function(...args) { // 创建原生函数 const fn = new OriginalFunction(...args); // 判断是否为箭头函数(箭头函数无prototype) if (!fn.prototype) { return (...callArgs) => { console.log('Proxy fn called!', callArgs); return fn(...callArgs); }; } // 包装普通函数 const wrappedFn = function() { console.log('Proxy fn called!', arguments); return fn.apply(this, arguments); }; // 标记已包装,避免重复处理 wrappedFn.__isWrapped__ = true; return wrappedFn; }; // 给已有全局函数批量添加拦截 for (const key in window) { const target = window[key]; if (typeof target === 'function' && !target.__isWrapped__) { const originalFn = target; window[key] = function() { console.log('Proxy fn called!', arguments); return originalFn.apply(this, arguments); }; window[key].__isWrapped__ = true; } }
2. 装饰器方案的局限性
装饰器目前处于提案阶段,即便用Babel插件,也只能在编译阶段对函数添加拦截逻辑,无法处理运行时动态创建的函数,也不能自动覆盖页面中已有的函数(除非你能控制所有代码的编译流程)。如果你的代码都是自主编写且能统一编译,装饰器可以用来规范新函数的参数校验,但无法满足“全局自动拦截所有已有和新函数”的核心需求,所以这个方向不符合你的诉求。
三、注意事项
- 箭头函数的特殊性:箭头函数无
prototype且this绑定固定,包装时要避免修改其this指向,用箭头函数的形式包裹即可保留特性。 - 避免无限递归:包装函数时添加
__isWrapped__标记,防止重复包装导致栈溢出。 - 内置函数限制:部分内置函数(如
console.log)依赖引擎内部实现,强行包装可能引发异常,需谨慎处理。
内容的提问来源于stack exchange,提问作者Maria
相关产品推荐
相关产品推荐

