我可以覆盖 Function 对象的行为,以便我可以在每次函数调用之前注入行为,然后照常进行吗?具体来说,(尽管总体思路本身很有趣)我可以在每次函数调用时登录到控制台,而不必在任何地方插入 console.log 语句吗?然后正常的行为会继续吗?
我确实认识到这可能会产生严重的性能问题;即使在我的开发环境中,我也不打算通常运行此程序。但是,如果它有效,那么在运行代码上获得 1000 米的视图似乎是一个优雅的解决方案。我怀疑这个答案会让我更深入地了解 javascript。
我可以覆盖 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 ,而不是因为我们要拦截的调用。
这似乎是人们提出的第一个想法。有些比其他的更成功,但这里有一个有效的实现:
// 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!
重要的是这不是从控制台运行。各种浏览器有各种控制台工具,呼叫.CALL自己有很多,其中包括一次对于每个输入,这可能会混淆在当下的用户。另一个错误是只使用 console.log 参数,它通过控制台 api 进行字符串化,从而导致无限循环。
那么,申请呢?它们是我们拥有的唯一神奇的调用函数,所以让我们也尝试一下。这是一个同时捕获两者的版本:
// 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 会迅速导致无限循环(如果给它一个非字符串,console.log 将在内部使用 .call)。
我得到了一些结果并且没有页面崩溃,如下所示:
(function () {
var
origCall = Function.prototype.call,
log = document.getElementById ('call_log');
// Override call only if call_log element is present
log && (Function.prototype.call = function (self) {
var r = (typeof self === 'string' ? '"' + self + '"' : self) + '.' + this + ' (';
for (var i = 1; i < arguments.length; i++) r += (i > 1 ? ', ' : '') + arguments[i];
log.innerHTML += r + ')<br/>';
this.apply (self, Array.prototype.slice.apply (arguments, [1]));
});
}) ();
仅在 Chrome 9.xxx 版中测试。
它当然不会记录所有函数调用,但会记录一些!我怀疑只处理对“调用”自身的实际调用
只是一个快速测试,但它似乎对我有用。这种方式可能没有用,但我基本上是在替代者的身体中恢复原型,然后在退出之前“恢复”它。
这个例子简单地记录了所有的函数调用——尽管可能有一些我还没有发现的致命缺陷;在喝咖啡休息时这样做
callLog = [];
/* set up an override for the Function call prototype
* @param func the new function wrapper
*/
function registerOverride(func) {
oldCall = Function.prototype.call;
Function.prototype.call = func;
}
/* restore you to your regular programming
*/
function removeOverride() {
Function.prototype.call = oldCall;
}
/* a simple example override
* nb: if you use this from the node.js REPL you'll get a lot of buffer spam
* as every keypress is processed through a function
* Any useful logging would ideally compact these calls
*/
function myCall() {
// first restore the normal call functionality
Function.prototype.call = oldCall;
// gather the data we wish to log
var entry = {this:this, name:this.name, args:{}};
for (var key in arguments) {
if (arguments.hasOwnProperty(key)) {
entry.args[key] = arguments[key];
}
}
callLog.push(entry);
// call the original (I may be doing this part naughtily, not a js guru)
this(arguments);
// put our override back in power
Function.prototype.call = myCall;
}
我遇到了一些问题,包括在一个大粘贴中调用这个,所以这是我在 REPL 中输入的内容,以测试上述功能:
/* example usage
* (only tested through the node.js REPL)
*/
registerOverride(myCall);
console.log("hello, world!");
removeOverride(myCall);
console.log(callLog);
您可以覆盖Function.prototype.call
,只需确保仅apply
在您的覆盖范围内起作用。
window.callLog = [];
Function.prototype.call = function() {
Array.prototype.push.apply(window.callLog, [[this, arguments]]);
return this.apply(arguments[0], Array.prototype.slice.apply(arguments,[1]));
};