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?

Ray Lu
fonte
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());
}
Greg Hewgill
fonte
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).

Mariano Desanze
fonte
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.

Phil
fonte
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 :(

nourdine
fonte
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.

ale5000
fonte
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)

heystewart
fonte
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
gomas
fonte
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

Greg
fonte
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();
}
inorganik
fonte
Isso está funcionando muito bem! deve por aceitos como a resposta certa, como outras formas são velhos \ não funcionam mais
Yuval Pruss
19
function Hello() {
    alert(Hello.caller);
}
Shadow2531
fonte
1
E apenas para o nome da função use Hello.caller.name
vanval
O mesmo quearguments.callee.caller.toString()
user2720864
Esta deve ser a resposta correta, pelo menos para 2016
Daniel
Isto não é em uma pista de normas, mas vai trabalhar a partir de ECMAScript 5.
Obinna Nwakwue
1
@ Daniel: não, não deveria. Veja developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Dan Dascalescu 4/19/19
18

É mais seguro de usar, *arguments.callee.callerpois arguments.callerestá obsoleto ...

Pablo Cabrera
fonte
36
arguments.calleetambém foi descontinuado no ES5 e removido no modo estrito.
precisa saber é o seguinte
2
Existe uma alternativa? Edit: arguments.calleefoi uma solução ruim para um problema que agora foi melhor resolvido developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Matthew
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('.');
  };
}
QueueHammer
fonte
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()

Prasanna
fonte
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.

VanagaS
fonte
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();
}());

Rovanion
fonte
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.

bladnman
fonte
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]);
JoolzCheat
fonte
1
Isso foi suspenso por muitos anos .
Dan Dascalescu
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.

Pablo Armentano
fonte
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>
BrazFlat
fonte
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;
}

fonte
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.

Stephen Quan
fonte
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
    }
    */
}
ns16
fonte
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.

Diego Augusto Molina
fonte
Tente isso para funções recursivas.
Daniel1426
arguments.calleefoi descontinuado por muitos anos .
Dan Dascalescu
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á.

GrayedFox
fonte
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 :).

Abrar Jahin
fonte
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)]
Anônimo
fonte
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();

autista
fonte
Bom truque, mas não funcionará para os módulos ES5, que estão inteiramente no modo estrito .
Dan Dascalescu 4/01/19
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
}
Mario PG
fonte
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
JiarongWu
fonte