我可以重写Javascript函数对象以记录所有函数调用吗?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (6)

我可以重写Function对象的行为,以便我可以在每次函数调用之前注入行为,然后继续正常进行?具体来说(尽管总体思路本身很吸引人),我可以在每次函数调用时都登录到控制台,而不必在任何地方插入console.log语句?然后正常的行为继续?

我确实认识到这可能会有重大的性能问题; 即使在我的开发环境中,我也不打算进行这种运行。但是,如果它能够运行,它似乎是一个优雅的解决方案,可以在运行代码上获得1000米视图。我怀疑这个答案会让我更深入地了解JavaScript。

提问于
用户回答回答于

显而易见的答案如下所示:

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);
};

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

用户回答回答于

拦截函数调用

这里有很多人试图覆盖.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);
};

这成功拦截了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!

这不是从控制台运行非常重要。各种浏览器都有各种各样的调用工具,可以调用自己很多,包括每次输入一次,这可能会混淆用户。另一个错误就是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);
}

...让我们试试吧!

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

正如你所看到的,调用的括号不会被注意到。

结论

幸运的是,调用括号不能从JavaScript中截取。但即使.call会拦截函数对象的括号运算符,我们如何在不引起无限循环的情况下调用原始函数呢?

覆盖.call / .apply的唯一功能就是拦截对这些原型函数的显式调用。如果控制台与这种黑客一起使用,将会有大量垃圾邮件。如果使用控制台API,则必须非常小心,因为使用控制台API可能会快速导致无限循环(如果给出非串的话,console.log将在内部使用.call)。

扫码关注云+社区