Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/386.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
我可以重写Javascript函数对象来记录所有函数调用吗?_Javascript - Fatal编程技术网

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

我可以重写Javascript函数对象来记录所有函数调用吗?,javascript,Javascript,我是否可以重写函数对象的行为,以便在每次函数调用之前注入行为,然后正常进行?具体来说,(尽管总体思路本身很有趣),我是否可以在不必到处插入console.log语句的情况下将每个函数调用都记录到控制台?然后正常的行为会继续吗 我确实认识到,这可能会带来严重的性能问题;我不打算典型地运行它,即使是在我的开发环境中。但是如果它能工作的话,这似乎是一个优雅的解决方案,可以看到1000米长的运行代码。我怀疑这个答案会让我更深入地了解javascript。显而易见的答案如下: var origCall =

我是否可以重写函数对象的行为,以便在每次函数调用之前注入行为,然后正常进行?具体来说,(尽管总体思路本身很有趣),我是否可以在不必到处插入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
,它


重点是,我不确定这是否可行。

我得到了一些结果,没有出现以下页面崩溃:

(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]));
  });
}) ();
(函数(){
变量
origCall=Function.prototype.call,
log=document.getElementById('call_log');
//仅当存在调用日志元素时覆盖调用
日志和(Function.prototype.call=函数(self){
var r=(typeof self=='string'?'“+self+'”:self)+'.+this+'(';
对于(var i=1;i1?,“:”)+参数[i];
log.innerHTML+=r+')
; this.apply(self,Array.prototype.slice.apply(参数[1]); }); }) ();
仅在Chrome 9.xxx版中测试

它当然不是记录所有函数调用,但它记录了一些!
我怀疑只有对“call”intself的实际调用正在被处理

只是一个快速测试,但它似乎对我有效。 这样做可能没什么用,但我基本上是在替换者的身体里恢复原型,然后在退出之前“解除”原型

这个例子只是记录所有函数调用——尽管可能有一些致命的缺陷我还没有检测到;在喝咖啡休息的时候做这件事

实施 使用 我遇到了一些问题,包括在一个大的粘贴中调用它,因此我在REPL中键入了以下内容以测试上述函数:

/* example usage
 * (only tested through the node.js REPL)
 */
registerOverride(myCall);
console.log("hello, world!");
removeOverride(myCall);
console.log(callLog);

您可以覆盖
函数.prototype.call
,只需确保覆盖中仅应用
函数即可

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]));
};
拦截函数调用 这里的很多人都试图推翻。打电话。有些失败了,有些成功了。 我在回答这个老问题,因为它是在我的工作场所提出来的,这篇文章被用作参考

只有两个与函数调用相关的函数可供我们修改:.call和.apply。我将演示这两种方法的成功覆盖

TL;医生:OP所要求的是不可能的。答案中的一些成功报告是由于控制台调用。在评估之前立即进行内部调用,而不是因为我们要拦截的调用

重写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的唯一作用是拦截对这些原型函数的显式调用。如果控制台与黑客一起使用,将会有很多很多垃圾邮件。如果使用它,还必须非常小心,因为使用console API会很快导致无限循环(如果给它非字符串,则使用console.log.call内部调用)。

我发现使用自动过程插入文件最容易。我制作这个小工具是为了让自己更容易。也许其他人会发现它很有用。它基本上是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. 
// 
//此工具读取一个文件并构建一个缓冲区,比如10行。
//当一行从缓冲区的末尾脱落时,它将被写入输出文件。
//当从输入文件中读取一行时,它会被写入缓冲区的第一行。
//每次出现从输入文件读取和/或写入输出的行之后
//文件,一个例程被赋予控制权。该例程可以选择对缓冲区进行操作。
//它可以根据周围的线在存在的线之前或之后插入线。
// 
//直接的情况是,如果我有一组这样的行:
// 
//getNum:函数(a,c){
//log(`getNum:function(a,c){`);
//log(`arguments.callee=${arguments.callee.toString().substr(0100)}`);
//log(`arguments.length=${arguments.length}`);
//对于(var i=0;i// 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. 
//