我是否可以覆盖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。我将演示对它们的成功覆盖。
简而言之,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!
// 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
origCall.apply(this, arguments);
。 - Sen Jacob(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版本中进行过测试。
它肯定不会记录所有的函数调用,但是它确实记录了一些!我怀疑只有对'call'本身的实际调用才会被处理。
[object Arguments].slice() source: at Function.function_prototype_call_override (<anonymous>:76:17) [object HTMLBodyElement].anonymous ((object)[object Object], (object)[object Object],[object Object]) source: at Function.function_prototype_call_override (<anonymous>:76:17)
- Hanna只是一个快速测试,但在我的机器人身体中恢复原型并在退出之前“取消还原”,似乎对我有效。
这个示例仅记录所有函数调用 - 虽然可能存在我尚未检测到的致命缺陷; 在休息时间完成。
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]));
};
我发现使用自动化过程来对文件进行仪表化是最容易的。我建立了这个小工具,使得对我自己来说更容易。也许其他人也会觉得有用。它基本上是awk,但更容易让Javascript程序员使用。
// This tool reads a file and builds a buffer of say ten lines.
// When a line falls off the end of the buffer, it gets written to the output file.
// When a line is read from the input file, it gets written to the first line of the buffer.
// After each occurrence of a line being read from the input file and/or written to the output
// file, a routine is given control. The routine has the option of operating on the buffer.
// It can insert a line before or after a line that is there, based on the lines surrounding.
//
// The immediate case is that if I have a set of lines like this:
//
// getNum: function (a, c) {
// console.log(`getNum: function (a, c) {`);
// console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
// console.log(`arguments.length = ${arguments.length}`);
// for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); }
// var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null;
// return d ? d[0] : null
// },
// compareNums: function (a, c, d) {
// console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
//
// I want to change that to a set of lines like this:
//
// getNum: function (a, c) {
// console.log(`getNum: function (a, c) {`);
// console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
// console.log(`arguments.length = ${arguments.length}`);
// for (var i = 0; i < arguments.length; i++) { console.log(`arguments[${i}] = ${arguments[i] ? arguments[i].toString().substr(0,100) : 'falsey'}`); }
// var d = b.isStrNum(a) ? (c && b.isString(c) ? RegExp(c) : b.getNumRegx).exec(a) : null;
// return d ? d[0] : null
// },
// compareNums: function (a, c, d) {
// console.log(`compareNums: function (a, c, d) {`);
// console.log(`arguments.callee = ${arguments.callee.toString().substr(0,100)}`);
//
// We are trying to figure out how a set of functions work, and I want each function to report
// its name when we enter it.
//
// To save time, options and the function that is called on each cycle appear at the beginning
// of this file. Ideally, they would be --something options on the command line.
const readline = require('readline');
//------------------------------------------------------------------------------------------------
// Here are the things that would properly be options on the command line. Put here for
// speed of building the tool.
const frameSize = 10;
const shouldReportFrame = false;
function reportFrame() {
for (i = frame.length - 1; i >= 0; i--) {
console.error(`${i}. ${frame[i]}`); // Using the error stream because the stdout stream may have been coopted.
}
}
function processFrame() {
// console.log(`******** ${frame[0]}`);
// if (frame[0].search('console.log(\`arguments.callee = \$\{arguments.callee.toString().substr(0,100)\}\`);') !== -1) {
// if (frame[0].search('arguments.callee') !== -1) {
// if (frame[0].search(/console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/) !== -1) {
var matchArray = frame[0].match(/([ \t]*)console.log\(`arguments.callee = \$\{arguments.callee.toString\(\).substr\(0,100\)\}`\);/);
if (matchArray) {
// console.log('******** Matched');
frame.splice(1, 0, `${matchArray[1]}console.log('${frame[1]}');`);
}
}
//------------------------------------------------------------------------------------------------
var i;
var frame = [];
const rl = readline.createInterface({
input: process.stdin
});
rl.on('line', line => {
if (frame.length > frameSize - 1) {
for (i = frame.length - 1; i > frameSize - 2; i--) {
process.stdout.write(`${frame[i]}\n`);
}
}
frame.splice(frameSize - 1, frame.length - frameSize + 1);
frame.splice(0, 0, line);
if (shouldReportFrame) reportFrame();
processFrame();
// process.stdout.write(`${line}\n`); // readline gives us the line with the newline stripped off
});
rl.on('close', () => {
for (i = frame.length - 1; i > -1; i--) {
process.stdout.write(`${frame[i]}\n`);
}
});
// Notes
//
// We are not going to control the writing to the output stream. In particular, we are not
// going to listen for drain events. Nodejs' buffering may get overwhelmed.
//