关于callstack:你如何在JavaScript中找到调用函数?

How do you find out the caller function in JavaScript?

1
2
3
4
5
6
7
8
9
function main()
{
   Hello();
}

function Hello()
{
  // How do you find out the caller function is 'main'?
}

有没有办法找出调用堆栈?


1
2
3
4
function Hello()
{
    alert("caller is" + Hello.caller);
}

请注意,此功能是非标准的,来自Function.caller

Non-standard
This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

下面是2008年的旧答案,现代JavaScript不再支持它:

1
2
3
4
function Hello()
{
    alert("caller is" + arguments.callee.caller.toString());
}


堆栈跟踪

可以使用特定于浏览器的代码查找整个堆栈跟踪。好消息是已经有人做了;这是Github上的项目代码。

但并非所有的消息都是好消息:

  • 获取堆栈跟踪真的很慢,所以要小心(阅读本文了解更多信息)。

  • 您需要定义函数名,以便堆栈跟踪清晰易读。因为如果你有这样的代码:

    1
    2
    3
    4
    5
    6
    var Klass = function kls() {
       this.Hello = function() { alert(printStackTrace().join('

    '
    )); };
    }
    new Klass().Hello();

    google chrome会提醒... kls.Hello ( ...,但大多数浏览器都希望在关键字function后面有一个函数名,并将其视为匿名函数。如果您不给函数命名为kls,那么即使不是chrome也可以使用Klass名称。

    顺便说一下,您可以将printstacktrace选项{guess: true}传递给函数printstacktrace,但这样做并没有发现任何真正的改进。

  • 并非所有浏览器都提供相同的信息。即参数、代码列等。

  • 调用方函数名

    顺便说一下,如果您只需要调用函数的名称(在大多数浏览器中,而不是IE),您可以使用:

    1
    arguments.callee.caller.name

    但请注意,该名称将是function关键字后面的名称。我发现(甚至在Google Chrome上)没有办法在不获取整个函数的代码的情况下获得更多。

    调用方函数代码

    总结其他最好的答案(由Pablo Cabrera、Nourdine和Greg Hewgill)。您唯一可以使用的跨浏览器和真正安全的东西是:

    1
    arguments.callee.caller.toString();

    它将显示调用函数的代码。遗憾的是,这对我来说还不够,这就是为什么我会给你关于stacktrace和调用者函数名的提示(尽管它们不是跨浏览器)。


    您可以获得完整的stacktrace:

    1
    2
    3
    arguments.callee.caller
    arguments.callee.caller.caller
    arguments.callee.caller.caller.caller

    直到呼叫方是null

    注意:它在递归函数上导致无限循环。


    重述(并使其更清晰)

    此代码:

    1
    2
    3
    function Hello() {
        alert("caller is" + arguments.callee.caller.toString());
    }

    相当于:

    1
    2
    3
    function Hello() {
        alert("caller is" + Hello.caller.toString());
    }

    显然,第一个位更易于移植,因为您可以将函数名从"hello"更改为"ciao",并且仍然可以使整个函数工作。

    在后一种情况下,如果您决定重构被调用函数(hello)的名称,则必须更改其所有出现的情况:(


    我知道你提到过"在javascript中",但是如果目的是调试,我认为只使用浏览器的开发人员工具会更容易。这就是它在铬合金中的外观:enter image description here只需将调试器放到要调查堆栈的位置即可。


    我通常在铬合金中使用(new Error()).stack。好的是,它还提供了调用方调用函数的行号。缺点是它将堆栈的长度限制为10,这就是为什么我首先来到这个页面的原因。

    (我正在使用它在执行期间收集低级构造函数中的调用堆栈,以便稍后查看和调试,因此设置断点是没有用的,因为它将被命中数千次)


    如果您不打算在IE<11中运行它,那么console.trace()将适合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function main() {
        Hello();
    }

    function Hello() {
        console.trace()
    }

    main()
    // Hello @ VM261:9
    // main @ VM261:4

    您可以使用function.caller获取调用函数。使用argument.caller的旧方法被认为已过时。

    下面的代码说明了它的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Hello() { return Hello.caller;}

    Hello2 = function NamedFunc() { return NamedFunc.caller; };

    function main()
    {
       Hello();  //both return main()
       Hello2();
    }

    关于过时参数的说明。调用方:https://developer.mozilla.org/en-us/docs/web/javascript/reference/functions/arguments/caller

    注意函数。调用者不是标准的:https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/function/caller


    使用*arguments.callee.caller更安全,因为arguments.caller被否决了…


    1
    2
    3
    function Hello() {
        alert(Hello.caller);
    }


    看起来这是一个很好解决的问题,但我最近发现,在"严格模式"下不允许被调用方使用,因此为了我自己的使用,我编写了一个类,它将从调用路径中获取路径。它是小helper lib的一部分,如果要使用代码独立,请更改用于返回调用方堆栈跟踪的偏移量(使用1而不是2)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    function ScriptPath() {
      var scriptPath = '';
      try {
        //Throw an error to generate a stack trace
        throw new Error();
      }
      catch(e) {
        //Split the stack trace into each line
        var stackLines = e.stack.split('
    '
    );
        var callerIndex = 0;
        //Now walk though each line until we find a path reference
        for(var i in stackLines){
          if(!stackLines[i].match(/http[s]?:\/\//)) continue;
          //We skipped all the lines with out an http so we now have a script reference
          //This one is the class constructor, the next is the getScriptPath() call
          //The one after that is the user code requesting the path info (so offset by 2)
          callerIndex = Number(i) + 2;
          break;
        }
        //Now parse the string for each section we want to return
        pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
      }

      this.fullPath = function() {
        return pathParts[1];
      };

      this.path = function() {
        return pathParts[2];
      };

      this.file = function() {
        return pathParts[3];
      };

      this.fileNoExt = function() {
        var parts = this.file().split('.');
        parts.length = parts.length != 1 ? parts.length - 1 : 1;
        return parts.join('.');
      };
    }


    我会这样做:

    1
    2
    3
    function Hello() {
      console.trace();
    }

    尝试访问此:

    1
    arguments.callee.caller.name


    只需控制台记录您的错误堆栈。然后你就可以知道你是怎么被叫来的了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const hello = () => {
      console.log(new Error('I was called').stack)
    }

    const sello = () => {
      hello()
    }

    sello()


    我想在这里添加我的小提琴:

    http://jsfiddle.net/bladnman/ehum3/

    我测试了这是Chrome,Safari和IE(10和8)。工作良好。只有一个功能很重要,所以如果你被大小提琴吓到了,请阅读下面的内容。

    注:这把小提琴里有相当数量的我自己的"样板"。如果你愿意的话,你可以去掉所有这些,使用split。这只是我开始依赖的一套超安全的功能。

    这里还有一个"jsFiddle"模板,我在很多小提琴中使用它来简单地快速地进行小提琴。


    2018更新

    严格禁止使用caller。这里有一个使用(非标准)Error堆栈的替代方案。

    下面的函数似乎在firefox 52和chrome 61-71中完成了这项工作,尽管它的实现对两个浏览器的日志记录格式做了很多假设,但是应该谨慎使用,因为它抛出了一个异常,并且可能在完成之前执行两个regex匹配。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    'use strict';
    const fnNameMatcher = /([^(]+)@|at ([^(]+) \(/;

    function fnName(str) {
      const regexResult = fnNameMatcher.exec(str);
      return regexResult[1] || regexResult[2];
    }

    function log(...messages) {
      const logLines = (new Error().stack).split('
    '
    );
      const callerName = fnName(logLines[1]);

      if (callerName !== null) {
        if (callerName !== 'log') {
          console.log(callerName, 'called with:', ...messages);
        } else {
          console.log(fnName(logLines[2]), 'called with:', ...messages);
        }
      } else {
        console.log(...messages);
      }
    }

    function foo() {
      log('hi', 'there');
    }

    (function main() {
      foo();
    }());


    如果只需要函数名而不需要代码,并且需要独立于浏览器的解决方案,请使用以下选项:

    1
    var callerFunction = arguments.callee.caller.toString().match(/function ([^\(]+)/)[1];

    请注意,如果数组中没有[1]元素,而没有调用函数,则上面的内容将返回一个错误。要解决此问题,请使用以下方法:

    1
    var callerFunction = (arguments.callee.caller.toString().match(/function ([^\(]+)/) === null) ? 'Document Object Model': arguments.callee.caller.toString().match(/function ([^\(]+)/)[1], arguments.callee.toString().match(/function ([^\(]+)/)[1]);


    只想让你知道,在PhoneGap/Android上,name似乎不起作用。但江户十一〔一〕会耍花招的。


    在这里,除了functionname以外的所有东西都是从caller.toString()中剥离出来的,带有regexp。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <meta charset="UTF-8">
    Show the callers name<!-- This validates as html5! -->

    main();
    function main() { Hello(); }
    function Hello(){
      var name = Hello.caller.toString().replace(/\s\([^#]+$|^[^\s]+\s/g,'');
      name = name.replace(/\s/g,'');
      if ( typeof window[name] !== 'function' )
        alert ("sorry, the type of"+name+" is"+ typeof window[name]);
      else
        alert ("The name of the"+typeof window[name]+" that called is"+name);
    }


    以下是获取完整stacktrace的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function stacktrace() {
    var f = stacktrace;
    var stack = 'Stack trace:';
    while (f) {
      stack += '
    '
    + f.name;
      f = f.caller;
    }
    return stack;
    }


    Heystewart的回答和Jiarongwu的回答都提到,Error对象可以访问stack

    下面是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function main() {
      Hello();
    }

    function Hello() {
      var stack;
      try {
        throw new Error();
      } catch (e) {
        stack = e.stack;
      }
      // N.B. stack ==="Error
      at Hello ...
      at main ...
    ...."
      var m = stack.match(/.*?Hello.*?
    (.*?)
    /);
      if (m) {
        var caller_name = m[1];
        console.log("
    Caller is:", caller_name)
      }
    }

    main();

    不同的浏览器以不同的字符串格式显示堆栈:


    Safari : Caller is: main@https://stacksnippets.net/js:14:8
    Firefox : Caller is: main@https://stacksnippets.net/js:14:3
    Chrome : Caller is: at main (https://stacksnippets.net/js:14:3)
    IE Edge : Caller is: at main (https://stacksnippets.net/js:14:3)
    IE : Caller is: at main (https://stacksnippets.net/js:14:3)

    大多数浏览器将使用var stack = (new Error()).stack设置堆栈。在Internet Explorer中,堆栈将未定义-您必须抛出一个真正的异常才能检索堆栈。

    结论:使用Error对象中的stack可以确定主叫方是"hello"。事实上,在calleecaller方法不起作用的情况下,它会起作用。它还将显示上下文,即源文件和行号。但是,需要努力使解决方案跨平台。


    为什么上面所有的解决方案看起来都像火箭科学。同时,它不应该比这个片段更复杂。所有的功劳都归功于这个家伙

    如何在javascript中找到调用函数?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var stackTrace = function() {

        var calls = [];
        var caller = arguments.callee.caller;

        for (var k = 0; k < 10; k++) {
            if (caller) {
                calls.push(caller);
                caller = caller.caller;
            }
        }

        return calls;
    };

    // when I call this inside specific method I see list of references to source method, obviously, I can add toString() to each call to see only function's content
    // [function(), function(data), function(res), function(l), function(a, c), x(a, b, c, d), function(c, e)]


    解决这个问题的另一种方法是简单地将调用函数的名称作为参数传递。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    function reformatString(string, callerName) {

        if (callerName ==="uid") {
            string = string.toUpperCase();
        }

        return string;
    }

    现在,您可以这样调用函数:

    1
    2
    3
    4
    5
    function uid(){
        var myString ="apples";

        reformatString(myString, function.name);
    }

    我的示例使用了对函数名的硬编码检查,但是您可以很容易地使用switch语句或其他逻辑来执行您想要的操作。


    据我所知,我们有两种方法可以解决这个问题-

  • arguments.caller参数

    1
    2
    3
    4
    5
    6
    7
    function whoCalled()
    {
        if (arguments.caller == null)
           console.log('I was called from the global scope.');
        else
           console.log(arguments.caller + ' called me!');
    }
  • 调用函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function myFunc()
    {
       if (myFunc.caller == null) {
          return 'The function was called from the top!';
       }
       else
       {
          return 'This function\'s caller was ' + myFunc.caller;
        }
    }
  • 想你有你的答案:)。


    我正试图用这个问题来解决这个问题和目前的赏金问题。

    赏金要求在严格模式下获取调用者,我能看到这一点的唯一方法是引用在严格模式之外声明的函数。

    例如,以下内容是非标准的,但已通过之前(2016年3月29日)和当前(2018年8月1日)版本的Chrome、Edge和Firefox进行测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function caller()
    {
       return caller.caller.caller;
    }

    'use strict';
    function main()
    {
       // Original question:
       Hello();
       // Bounty question:
       (function() { console.log('Anonymous function called by ' + caller().name); })();
    }

    function Hello()
    {
       // How do you find out the caller function is 'main'?
       console.log('Hello called by ' + caller().name);
    }

    main();


    尝试以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function getStackTrace(){
      var f = arguments.callee;
      var ret = [];
      var item = {};
      var iter = 0;

      while ( f = f.caller ){
          // Initialize
        item = {
          name: f.name || null,
          args: [], // Empty array = no arguments passed
          callback: f
        };

          // Function arguments
        if ( f.arguments ){
          for ( iter = 0; iter<f.arguments.length; iter++ ){
            item.args[iter] = f.arguments[iter];
          }
        } else {
          item.args = null; // null = argument listing not supported
        }

        ret.push( item );
      }
      return ret;
    }

    在firefox-21和chromium-25中为我工作。


    我认为下面的代码可能会有所帮助:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    window.fnPureLog = function(sStatement, anyVariable) {
        if (arguments.length < 1) {
            throw new Error('Arguments sStatement and anyVariable are expected');
        }
        if (typeof sStatement !== 'string') {
            throw new Error('The type of sStatement is not match, please use string');
        }
        var oCallStackTrack = new Error();
        console.log(oCallStackTrack.stack.replace('Error', 'Call Stack:'), '
    '
    + sStatement + ':', anyVariable);
    }

    执行代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    window.fnPureLog = function(sStatement, anyVariable) {
        if (arguments.length < 1) {
            throw new Error('Arguments sStatement and anyVariable are expected');
        }
        if (typeof sStatement !== 'string') {
            throw new Error('The type of sStatement is not match, please use string');
        }
        var oCallStackTrack = new Error();
        console.log(oCallStackTrack.stack.replace('Error', 'Call Stack:'), '
    '
    + sStatement + ':', anyVariable);
    }

    function fnBsnCallStack1() {
        fnPureLog('Stock Count', 100)
    }

    function fnBsnCallStack2() {
        fnBsnCallStack1()
    }

    fnBsnCallStack2();

    日志如下:

    1
    2
    3
    4
    5
    6
    Call Stack:
        at window.fnPureLog (:8:27)
        at fnBsnCallStack1 (:13:5)
        at fnBsnCallStack2 (:17:5)
        at :20:1
    Stock Count: 100

    由于之前的答案都不像我所寻找的那样工作(只得到最后一个函数调用者,而不是字符串或调用堆栈函数),所以我将我的解决方案发布在这里,以帮助像我这样的人,并希望这对他们有用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function getCallerName(func)
    {
      if (!func) return"anonymous";
      let caller = func.caller;
      if (!caller) return"anonymous";
      caller = caller.toString();
      if (!caller.trim().startsWith("function")) return"anonymous";
      return caller.substring(0, caller.indexOf("(")).replace("function","");
    }


    //  Example of how to use"getCallerName" function

    function Hello(){
    console.log("ex1  => " + getCallerName(Hello));
    }

    function Main(){
    Hello();

    // another example
    console.log("ex3  => " + getCallerName(Main));
    }

    Main();


    如果您出于某种原因确实需要该功能,并希望它与跨浏览器兼容,而不必担心严格的内容和向前兼容,请传递以下引用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function main()
    {
       Hello(this);
    }

    function Hello(caller)
    {
        // caller will be the object that called Hello. boom like that...
        // you can add an undefined check code if the function Hello
        // will be called without parameters from somewhere else
    }