我可以覆盖Javascript Function对象来记录所有函数调用吗?

Mat*_*ols 50 javascript

我可以覆盖Function对象的行为,以便我可以在每个函数调用之前注入行为,然后继续正常吗?具体来说,(虽然一般的想法本身很有趣)我可以在每个函数调用时登录到控制台而无需在任何地方插入console.log语句吗?那么正常的行为还在继续吗?

我确实认识到这可能会产生严重的性能问题; 即使在我的开发环境中,我也无意进行此操作.但如果它工作,似乎是一个优雅的解决方案,以获得运行代码1000米的视图.我怀疑答案将向我展示更深入的javascript.

etl*_*ett 22

显而易见的答案如下:

var origCall = Function.prototype.call;
Function.prototype.call = function (thisArg) {
    console.log("calling a function");

    var args = Array.prototype.slice.call(arguments, 1);
    origCall.apply(thisArg, args);
};
Run Code Online (Sandbox Code Playgroud)

但这实际上立即进入一个无限循环,因为调用的行为console.log执行一个函数调用,该函数调用执行函数调用,调用console.log函数调用,console.log...

重点是,我不确定这是可能的.

  • 为了解决无限循环问题,我想你可以检查[`Function#caller`](http://mathias.html5.org/specs/javascript/#function.prototype.caller)是否等于`Function#call`在那种情况下跳过`console.log`调用. (8认同)
  • 实际上,它是触发递归的Array.prototype.slice.call(...),因为它是从Function.prototype继承而来的,你在那里重写了它.执行"console.log(...);"时不会使用Function.prototype.call(...) (8认同)

Ken*_*nny 15

拦截函数调用

许多人试图覆盖.call.有些人失败了,有些人成功了.我正在回答这个老问题,因为它已在我的工作场所提出,这篇文章被用作参考.

我们只能修改两个与函数调用相关的函数:.call和.apply.我将演示两者的成功覆盖.

TL; DR:OP提出的问题是不可能的.答案中的一些成功报告是由于控制台在评估之前在内部调用.call,而不是因为我们想要拦截的调用.

重写Function.prototype.call

这似乎是人们提出的第一个想法.有些比其他更成功,但这是一个有效的实现:

// Store the original
var origCall = Function.prototype.call;
Function.prototype.call = function () {
    // If console.log is allowed to stringify by itself, it will
    // call .call 9 gajillion times. Therefore, lets do it by ourselves.
    console.log("Calling",
                Function.prototype.toString.apply(this, []),
                "with:",
                Array.prototype.slice.apply(arguments, [1]).toString()
               );

    // A trace, for fun
   console.trace.apply(console, []);

   // The call. Apply is the only way we can pass all arguments, so don't touch that!
   origCall.apply(this, arguments);
};
Run Code Online (Sandbox Code Playgroud)

这成功拦截了Function.prototype.call

让它旋转一下,好吗?

// Some tests
console.log("1"); // Does not show up
console.log.apply(console,["2"]); // Does not show up
console.log.call(console, "3"); // BINGO!
Run Code Online (Sandbox Code Playgroud)

重要的是,这不是从控制台运行.各种浏览器有各种控制台工具调用.CALL自己有很多,其中包括一次对于每个输入,这可能会混淆在当下的用户.另一个错误是只有console.log参数,它通过控制台api进行字符串化,从而导致无限循环.

重写Function.prototype.apply

那么,申请呢?它们是我们唯一的魔术召唤功能,所以我们也尝试一下.这里有一个兼具两者的版本:

// Store apply and call
var origApply = Function.prototype.apply;
var origCall = Function.prototype.call;

// We need to be able to apply the original functions, so we need
// to restore the apply locally on both, including the apply itself.
origApply.apply = origApply;
origCall.apply = origApply;

// Some utility functions we want to work
Function.prototype.toString.apply = origApply;
Array.prototype.slice.apply = origApply;
console.trace.apply = origApply;

function logCall(t, a) {
    // If console.log is allowed to stringify by itself, it will
    // call .call 9 gajillion times. Therefore, do it ourselves.
    console.log("Calling",
                Function.prototype.toString.apply(t, []),
                "with:",
                Array.prototype.slice.apply(a, [1]).toString()
               );
    console.trace.apply(console, []);
}

Function.prototype.call = function () {
   logCall(this, arguments);
   origCall.apply(this, arguments);
};

Function.prototype.apply = function () {
    logCall(this, arguments);
    origApply.apply(this, arguments);
}
Run Code Online (Sandbox Code Playgroud)

......让我们试一试!

// Some tests
console.log("1"); // Passes by unseen
console.log.apply(console,["2"]); // Caught
console.log.call(console, "3"); // Caught
Run Code Online (Sandbox Code Playgroud)

如您所见,调用括号不会被注意到.

结论

幸运的是,调用括号不能从JavaScript中截取.但即使.call会拦截函数对象上的括号运算符,我们如何调用原始函数而不会导致无限循环?

重写.call/.apply唯一的做法是拦截对这些原型函数的显式调用.如果控制台与该黑客一起使用,则会有大量垃圾邮件.如果使用它,还必须非常小心,因为使用控制台API会很快导致无限循环(如果给一个非字符串,console.log将在内部使用.call).

  • 嗯,问题是这个 _used_ 在旧版本的引擎中工作。覆盖调用和应用用于捕获 _all_ 函数调用。这已在较新版本的浏览器中得到修复。 (2认同)