Como você descobre a função de chamada em JavaScript?


866
function main()
{
   Hello();
}

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

Existe uma maneira de descobrir a pilha de chamadas?


63
Espero que isso seja apenas para ajudá-lo na depuração. Comportamento variável com base no chamador é uma má ideia.
JO.

Quando isso seria útil para depuração?
Anderson Green

33
@AndersonGreen quando você tem, por exemplo, um método de renderização de modelo padrão e vê que está sendo chamado duas vezes. Em vez de vasculhar 1000s de LoC ou percorrer com o depurador, você pode apenas ver qual era a pilha na época.
tkone

28
para ver o rastreamento da pilha, use console.trace () para chrome. embora não conheça outros
lukas.pukenis 10/10

5
Por que isso é uma má ideia?
Jacob Schneider

Respostas:


995
function Hello()
{
    alert("caller is " + Hello.caller);
}

Observe que esse recurso não é padrão , de Function.caller:

Não padrão
Este recurso não é padrão e não está em uma faixa de padrões. Não o use em sites de produção voltados para a Web: ele não funcionará para todos os usuários. Também pode haver grandes incompatibilidades entre implementações e o comportamento pode mudar no futuro.


A seguir está a resposta antiga de 2008, que não é mais suportada no Javascript moderno:

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

254
arguments.callee.caller.nameobterá o nome da função.
Rocket Hazmat

137
"As propriedades 'chamador', 'chamado' e 'argumentos' podem não ser acessadas nas funções de modo estrito ou nos objetos de argumentos para chamadas para eles" - eles foram descontinuados no ES5 e removidos no modo estrito.
precisa

12
Só funcionará se você não estiver usando o modo estrito. Portanto, a remoção 'use strict';pode ajudar.
pvorb

23
argumentsPODE ser acessado de dentro de uma função no modo estrito, seria estúpido depreciar isso. apenas não da função. argumentos do lado de fora. Além disso, se você tiver um argumento nomeado, a forma de argumentos [i] dele não rastreará as alterações feitas na versão nomeada dentro da função.
rvr_jon

41
Este método tornou-se obsoleto desde que esta postagem foi listada em 2011. O método preferido agora é Function.caller, (a partir de 2015).
Greg

152

StackTrace

Você pode encontrar todo o rastreamento de pilha usando o código específico do navegador. O bom é que alguém já conseguiu ; Aqui está o código do projeto no GitHub .

Mas nem todas as notícias são boas:

  1. É muito lento para obter o rastreamento da pilha, portanto, tenha cuidado (leia isto para mais).

  2. Você precisará definir os nomes das funções para que o rastreamento da pilha seja legível. Porque se você tiver um código como este:

    var Klass = function kls() {
       this.Hello = function() { alert(printStackTrace().join('\n\n')); };
    }
    new Klass().Hello();

    O Google Chrome alerta, ... kls.Hello ( ...mas a maioria dos navegadores espera um nome de função logo após a palavra-chave functione o trata como uma função anônima. Nem mesmo o Chrome poderá usar o Klassnome se você não der o nomekls à função.

    E, a propósito, você pode passar para a função printStackTrace a opção, {guess: true}mas não encontrei nenhuma melhoria real ao fazer isso.

  3. Nem todos os navegadores fornecem as mesmas informações. Ou seja, parâmetros, coluna de código etc.


Nome da Função de Chamador

A propósito, se você deseja apenas o nome da função de chamada (na maioria dos navegadores, mas não no IE), pode usar:

arguments.callee.caller.name

Mas observe que esse nome será o nome da functionpalavra - chave. Não encontrei nenhuma maneira (mesmo no Google Chrome) de obter mais do que isso sem obter o código de toda a função.


Código da função de chamada

E resumindo o restante das melhores respostas (de Pablo Cabrera, nourdine e Greg Hewgill). A única coisa que você pode usar em vários navegadores e realmente segura é:

arguments.callee.caller.toString();

O que mostrará o código da função de chamada. Infelizmente, isso não é suficiente para mim, e é por isso que eu dou dicas para o StackTrace e a função de chamada Name (embora eles não sejam entre navegadores).


1
talvez você deve adicionar Function.callerper @ resposta de Greg
Zach Lysobey

Function.callerno entanto, não funcionará no modo estrito.
Rickard Elimää

54

Sei que você mencionou "em Javascript", mas se o objetivo é depurar, acho mais fácil usar as ferramentas de desenvolvedor do seu navegador. É assim que fica no Chrome: enter image description here basta soltar o depurador no local em que deseja investigar a pilha.


3
Essa é uma pergunta antiga ... mas essa é definitivamente a maneira mais moderna e válida de fazer isso hoje.
markstewie

53

Para recapitular (e torná-lo mais claro) ...

este código:

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

é equivalente a isso:

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

Claramente, o primeiro bit é mais portátil, já que você pode alterar o nome da função, digitar "Hello" para "Ciao" e ainda assim fazer com que tudo funcione.

Neste último caso você decida refatorar o nome da função invocada (Hello), será necessário alterar todas as ocorrências :(


7
arguments.callee.caller sempre nulo no Chrome 25.0.1364.5 dev
Kokizzu

53

Você pode obter o stacktrace completo:

arguments.callee.caller
arguments.callee.caller.caller
arguments.callee.caller.caller.caller

Até o chamador estar null.

Nota: causa um loop infinito em funções recursivas.


2
Desculpe pela resposta tardia, mas ainda não vi seu comentário; somente no caso de recursão não funciona, em outros casos deve funcionar.
Ale5000 13/05

45

Eu costumo usar (new Error()).stack no Chrome. O bom é que isso também fornece os números de linha em que o chamador chamou a função. A desvantagem é que limita o comprimento da pilha a 10, e é por isso que cheguei a esta página em primeiro lugar.

(Estou usando isso para coletar pilhas de chamadas em um construtor de baixo nível durante a execução, para exibir e depurar mais tarde, portanto, definir um ponto de interrupção não é útil, pois será atingido milhares de vezes)


Você poderia adicionar um pouco mais de descrição sobre a explicação que você fornece?
Abarisone

6
Esta é a única coisa que eu poderia começar a trabalhar quando 'use strict';estiver no local. Deu-me as informações de que precisava - obrigado!
Jeremy Harris

4
Em relação ao limite de tamanho da pilha ... você pode alterar isso com "Error.stackTraceLimit = Infinity".
Tom

(new Error ("StackLog")). stack.split ("\ n") facilita a leitura.
Teoman shipahi

36

Se você não for executá-lo no IE <11, o console.trace () será adequado.

function main() {
    Hello();
}

function Hello() {
    console.trace()
}

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

Está funcionando! Deve ser adicionado mais votos
positivos

22

Você pode usar o Function.Caller para obter a função de chamada. O método antigo usando o argumento.caller é considerado obsoleto.

O código a seguir ilustra seu uso:

function Hello() { return Hello.caller;}

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

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

Notas sobre argument.caller obsoleto: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/caller

Esteja ciente de que Function.caller não é padrão: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller


1
Esta é a resposta certa nos dias de hoje. Você não pode mais fazer argumentos.caller.callee. Gostaria que pudéssemos levar isso para o topo, já que todas as outras coisas estão desatualizadas agora.
coblr

4
Parece que isso não é possível no modo estrito? Cannot access caller property of a strict mode function
Zach Lysobey

Function.caller também não funcionou para mim no modo estrito. Além disso, de acordo com o MDN , function.caller não é padrão e não deve ser usado na produção. Pode funcionar para depuração, no entanto.
Jkdev 21/09/2015

Não tive nenhum problema com o não-padrão se ele funcionasse no Nó, mas simplesmente não é permitido no modo estrito (testei no nó 6.10). O mesmo se aplica aos 'argumentos'. Recebo a mensagem de erro: '' 'chamador' e 'argumentos' são propriedades de função restrita e não podem ser acessadas nesse contexto. "
Tom

21

Eu faria isso:

function Hello() {
  console.trace();
}

Isso está funcionando muito bem! deve por aceitos como a resposta certa, como outras formas são velhos \ não funcionam mais
Yuval Pruss



16

Parece que esta é uma pergunta bastante resolvida, mas recentemente descobri que o chamado não é permitido no 'modo estrito'; portanto, para meu próprio uso, escrevi uma classe que obterá o caminho de onde é chamada. Faz parte de uma pequena biblioteca auxiliar e, se você quiser usar o código independente, altere o deslocamento usado para retornar o rastreamento de pilha do chamador (use 1 em vez de 2)

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('\n');
    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('.');
  };
}

Não funciona para mim function a(){ function b(){ function c(){ return ScriptPath(); } return c(); } return b(); } a()no console (não tentei em um arquivo), mas parece ter uma ideia razoável. Deve ser votado de qualquer maneira para ter visibilidade.
Ninjagecko

A ideia é ótima. Estou analisando de maneira diferente, mas nos aplicativos nw.js, essa é realmente a única ideia que fornece o que estou procurando.
Andrew Grothe

Por favor, forneça um exemplo de como chamar esta função.
Pd_au 20/0618


11

Apenas console registre sua pilha de erros. Você pode saber como está sendo chamado

const hello = () => {
  console.log(new Error('I was called').stack)
}

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

sello()


10

Nos modos ES6 e Rigoroso, use o seguinte para obter a função Chamador

console.log((new Error()).stack.split("\n")[2].trim().split(" ")[1])

Observe que a linha acima lançará uma exceção, se não houver chamador ou pilha anterior. Use de acordo.


Para obter o chamado (o nome da função atual), use: console.log((new Error()).stack.split("\n")[1].trim().split(" ")[1])
VanagaS

10

Atualização de 2018

calleré proibido no modo estrito . Aqui está uma alternativa usando a Errorpilha (não padrão) .

A função a seguir parece fazer o trabalho no Firefox 52 e no Chrome 61-71, embora sua implementação faça muitas suposições sobre o formato de log dos dois navegadores e deva ser usada com cautela, pois gera uma exceção e possivelmente executa duas regex correspondências antes de serem feitas.

'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('\n');
  const callerName = fnName(logLines[1]);

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

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

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


4
Isso é incrível, e também horripilante.
Ian

Eu tenho "foo chamado com: oi lá", mas foo não foi chamado com "oi lá", log foi chamado com "oi lá"
AndrewR

Certo, havia um "modificador extraviado" na gramática da mensagem de erro. Significava dizer "log foi chamado a partir da função f, queria que a mensagem X fosse impressa", mas da maneira mais sucinta possível.
Rovanion 30/10/19

7

Eu queria adicionar meu violino aqui para isso:

http://jsfiddle.net/bladnman/EhUm3/

Eu testei isso é chrome, safari e IE (10 e 8). Funciona bem. Há apenas uma função que importa, portanto, se você se assusta com o violino, leia abaixo.

Nota: Há uma boa quantidade do meu próprio "clichê" neste violino. Você pode remover tudo isso e usar divisões, se quiser. É apenas um conjunto "ultra-seguro" de funções nas quais confiei.

Há também um modelo "JSFiddle" que eu uso para muitos violinos para mexer rapidamente.


Gostaria de saber se você poderia adicionar os "auxiliares" como extensões para o protótipo em alguns casos, por exemplo:String.prototype.trim = trim;
autistic

6

Se você deseja apenas o nome da função e não o código, e deseja uma solução independente do navegador, use o seguinte:

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

Observe que o descrito acima retornará um erro se não houver função de chamada, pois não há elemento [1] na matriz. Para contornar, use o abaixo:

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


5

Só quero que você saiba que no PhoneGap / Android o nameparece não estar funcionando. Mas arguments.callee.caller.toString()vai fazer o truque.


4

Aqui, tudo, exceto o, functionnameé retirado caller.toString(), com o RegExp.

<!DOCTYPE html>
<meta charset="UTF-8">
<title>Show the callers name</title><!-- This validates as html5! -->
<script>
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);
}
</script>

este ainda está retornando a declaração método inteiro
Maslow

4

aqui está uma função para obter o stacktrace completo :

function stacktrace() {
var f = stacktrace;
var stack = 'Stack trace:';
while (f) {
  stack += '\n' + f.name;
  f = f.caller;
}
return stack;
}

3

A resposta de heystewart e a resposta de JiarongWu mencionaram que o Errorobjeto tem acesso aostack .

Aqui está um exemplo:

function main() {
  Hello();
}

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

main();

Navegadores diferentes mostram a pilha em diferentes formatos de sequência:

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)

A maioria dos navegadores definirá a pilha com var stack = (new Error()).stack . No Internet Explorer, a pilha será indefinida - é necessário lançar uma exceção real para recuperar a pilha.

Conclusão: É possível determinar que "principal" é o responsável pela chamada "Olá" usando stacko Errorobjeto De fato, funcionará nos casos em que a abordagem callee/ callernão funcionar. Também mostrará o contexto, ou seja, arquivo de origem e número da linha. No entanto, é necessário esforço para tornar a solução entre plataformas.


2

Observe que você não pode usar o Function.caller no Node.js, use o pacote caller-id . Por exemplo:

var callerId = require('caller-id');

function foo() {
    bar();
}
function bar() {
    var caller = callerId.getData();
    /*
    caller = {
        typeName: 'Object',
        functionName: 'foo',
        filePath: '/path/of/this/file.js',
        lineNumber: 5,
        topLevelFlag: true,
        nativeFlag: false,
        evalFlag: false
    }
    */
}

1

Tente o seguinte código:

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

Trabalhou para mim no Firefox-21 e Chromium-25.


Tente isso para funções recursivas.
Daniel1426


1

Outra maneira de contornar esse problema é simplesmente passar o nome da função de chamada como um parâmetro.

Por exemplo:

function reformatString(string, callerName) {

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

    return string;
}

Agora, você pode chamar a função assim:

function uid(){
    var myString = "apples";

    reformatString(myString, function.name);
}

Meu exemplo usa uma verificação codificada do nome da função, mas você pode facilmente usar uma instrução switch ou alguma outra lógica para fazer o que deseja lá.


Acredito que isso também resolva problemas de compatibilidade entre navegadores, na maior parte. Mas teste isso antes de assumir que é verdade! ( começa a suar )
GrayedFox 4/16/16

1

Até onde eu sei, temos duas maneiras para isso, de fontes dadas como esta-

  1. argument.caller

    function whoCalled()
    {
        if (arguments.caller == null)
           console.log('I was called from the global scope.');
        else
           console.log(arguments.caller + ' called me!');
    }
  2. Function.caller

    function myFunc()
    {
       if (myFunc.caller == null) {
          return 'The function was called from the top!';
       }
       else
       {
          return 'This function\'s caller was ' + myFunc.caller;
        }
    }

Acho que você tem a sua resposta :).


Isso foi preterido por muitos anos e o Function.caller não funciona no modo estrito.
Dan Dascalescu

1

Por que todas as soluções acima parecem uma ciência de foguetes. Enquanto isso, não deve ser mais complicado que esse trecho. Todos os créditos para esse cara

Como você descobre a função de chamada em JavaScript?

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)]

3
É isso que eu uso ao usar isso: TypeError: 'caller', 'callee' e 'argumentos' propriedades não podem ser acessadas em funções de modo estrito ou objetos de argumentos para chamadas a elas. Alguma idéia de como trabalhar isso em modo estrito?
hard_working_ant

1

Estou tentando abordar a questão e a recompensa atual com essa pergunta.

A recompensa requer que o chamador seja obtido no modo estrito , e a única maneira de ver isso é fazendo referência a uma função declarada fora do modo estrito.

Por exemplo, o seguinte não é padrão, mas foi testado nas versões anterior (29/03/2016) e atual (1º de agosto de 2018) do Chrome, Edge e Firefox.

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



0

Se você realmente precisa da funcionalidade por algum motivo e deseja que ela seja compatível com vários navegadores, não se preocupe com coisas estritas e seja compatível com a frente, passe esta referência:

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
}

0

Acho que o seguinte pedaço de código pode ser útil:

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:'), '\n' + sStatement + ':', anyVariable);
}

Execute o código:

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:'), '\n' + sStatement + ':', anyVariable);
}

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

function fnBsnCallStack2() {
    fnBsnCallStack1()
}

fnBsnCallStack2();

O log fica assim:

Call Stack:
    at window.fnPureLog (<anonymous>:8:27)
    at fnBsnCallStack1 (<anonymous>:13:5)
    at fnBsnCallStack2 (<anonymous>:17:5)
    at <anonymous>:20:1 
Stock Count: 100
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.