¿Cómo se encuentra la función de llamada en JavaScript?

866
function main()
{
   Hello();
}

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

¿Hay alguna manera de averiguar la pila de llamadas?

Ray Lu
fuente
63
Espero que esto sea solo para ayudarlo a depurar. El comportamiento variable basado en la persona que llama es una mala idea.
DO
¿Cuándo sería esto útil para la depuración?
Anderson Green el
33
@AndersonGreen cuando tiene, por ejemplo, un método de representación de plantilla predeterminado y ve que se llama dos veces. En lugar de peinar a través de 1000 de LoC o pasar arduamente con el depurador, puede ver cuál era la pila en ese momento.
tkone
28
Para ver el seguimiento de la pila, utilice console.trace () para Chrome. sin embargo
lukas.pukenis
55
¿Por qué es una mala idea?
Jacob Schneider

Respuestas:

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

Tenga en cuenta que esta característica no es estándar , de Function.caller:

No estándar
Esta característica no es estándar y no está en una pista estándar. No lo use en sitios de producción orientados a la Web: no funcionará para todos los usuarios. También puede haber grandes incompatibilidades entre implementaciones y el comportamiento puede cambiar en el futuro.


La siguiente es la respuesta anterior de 2008, que ya no es compatible con Javascript moderno:

function Hello()
{
    alert("caller is " + arguments.callee.caller.toString());
}
Greg Hewgill
fuente
254
arguments.callee.caller.nameobtendrá el nombre de la función.
Rocket Hazmat
137
"No se puede acceder a las propiedades 'llamador', 'llamante' y 'argumentos' en las funciones de modo estricto o los objetos de argumentos para las llamadas a ellos", están en desuso en ES5 y se eliminan en modo estricto.
ThatGuy
12
Solo funcionará si no está utilizando el modo estricto. Entonces eliminar 'use strict';podría ayudar.
pvorb
23
argumentsSe puede acceder desde una función en modo estricto, sería estúpido desaprobar eso. simplemente no de la función. argumentos del exterior. Además, si tiene un argumento con nombre, la forma de los argumentos [i] no rastreará los cambios que realice en la versión con nombre dentro de la función.
rvr_jon
41
Este método se ha vuelto obsoleto desde que se publicó esta publicación en 2011. El método preferido ahora es Function.caller, (a partir de 2015).
Greg
152

StackTrace

Puede encontrar todo el seguimiento de la pila utilizando el código específico del navegador. Lo bueno es que alguien ya lo hizo ; Aquí está el código del proyecto en GitHub .

Pero no todas las noticias son buenas:

  1. Es muy lento obtener el seguimiento de la pila, así que tenga cuidado (lea esto para obtener más información).

  2. Deberá definir nombres de funciones para que el seguimiento de la pila sea legible. Porque si tienes un código como este:

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

    Google Chrome alertará ... kls.Hello ( ... pero la mayoría de los navegadores esperarán un nombre de función justo después de la palabra clave functiony lo tratarán como una función anónima. Ni siquiera Chrome podrá usar el Klassnombre si no le das el nombre klsa la función.

    Y, por cierto, puede pasar a la función printStackTrace la opción, {guess: true}pero no encontré ninguna mejora real al hacerlo.

  3. No todos los navegadores le brindan la misma información. Es decir, parámetros, columna de código, etc.


Nombre de la función del llamante

Por cierto, si solo desea el nombre de la función de llamada (en la mayoría de los navegadores, pero no IE) puede usar:

arguments.callee.caller.name

Pero tenga en cuenta que este nombre será el que aparece después de la functionpalabra clave. No encontré ninguna manera (incluso en Google Chrome) para obtener más que eso sin obtener el código de toda la función.


Código de función del llamante

Y resumiendo el resto de las mejores respuestas (por Pablo Cabrera, Nourdine y Greg Hewgill). Lo único que puede usar entre navegadores y realmente seguro es:

arguments.callee.caller.toString();

Que mostrará el código de la función de llamada. Lamentablemente, eso no es suficiente para mí, y es por eso que le doy consejos para StackTrace y el nombre de la función de llamada (aunque no son de navegador cruzado).

Mariano Desanze
fuente
1
tal vez deberías agregar Function.callersegún la respuesta de @ Greg
Zach Lysobey
Function.callerNo funcionará en modo estricto, sin embargo.
Rickard Elimää
54

Sé que mencionó "en Javascript", pero si el propósito es la depuración, creo que es más fácil usar las herramientas de desarrollador de su navegador. Así es como se ve en Chrome: ingrese la descripción de la imagen aquí simplemente suelte el depurador donde desee investigar la pila.

Phil
fuente
3
Esta es una vieja pregunta ... pero esta es definitivamente la forma moderna más válida de hacer esto hoy.
markstewie
53

Para recapitular (y hacerlo más claro) ...

este codigo:

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

es equivalente a esto:

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

Claramente, el primer bit es más portátil, ya que puede cambiar el nombre de la función, decir "Hola" a "Ciao", y aún así hacer que todo funcione.

En este último caso, en caso de que decida refactorizar el nombre de la función invocada (Hola), deberá cambiar todas sus apariciones :(

Nourdine
fuente
77
argumentos.callee.caller siempre nulo en Chrome 25.0.1364.5 dev
Kokizzu
53

Puede obtener el seguimiento completo de la pila:

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

Hasta que llama es null.

Nota: causa un bucle infinito en las funciones recursivas.

ale5000
fuente
2
Perdón por la respuesta tardía, pero no he visto tu comentario antes; solo para el caso de recursión no funciona, en otros casos debería funcionar.
ale5000
45

Yo suelo usar (new Error()).stack en Chrome. Lo bueno es que esto también le da los números de línea donde la persona que llama llamó a la función. La desventaja es que limita la longitud de la pila a 10, por eso llegué a esta página en primer lugar.

(Estoy usando esto para recopilar pilas de llamadas en un constructor de bajo nivel durante la ejecución, para verlas y depurarlas más tarde, por lo que establecer un punto de interrupción no es útil, ya que será golpeado miles de veces)

Heystewart
fuente
¿Podría agregar un poco más de descripción sobre la explicación que proporciona?
abarisone
66
Esto es lo único que podría llegar a trabajar cuando 'use strict';esté en su lugar. Me dio la información que necesitaba, ¡gracias!
Jeremy Harris
55
En cuanto al límite de longitud de la pila ... puede cambiar eso con "Error.stackTraceLimit = Infinity".
Tom
(nuevo error ("StackLog")). stack.split ("\ n") hace que sea más agradable de leer.
Teoman shipahi el
36

Si no va a ejecutarlo en IE <11, entonces console.trace () sería adecuado.

function main() {
    Hello();
}

function Hello() {
    console.trace()
}

main()
// Hello @ VM261:9
// main @ VM261:4
chicles
fuente
¡Esta funcionando! Debería agregarse más votos
positivos
22

Puede usar Function.Caller para obtener la función de llamada. El antiguo método que usa argumento.caller se considera obsoleto.

El siguiente código ilustra su uso:

function Hello() { return Hello.caller;}

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

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

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

Tenga en cuenta que Function.caller no es estándar: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller

Greg
fuente
1
Esta es la respuesta correcta en estos días. Ya no puedes hacer argumentos.caller.callee. Ojalá pudiéramos llevar esto a la cima ya que todas las demás cosas están desactualizadas ahora.
coblr
44
Parece que esto no es posible en modo estricto? Cannot access caller property of a strict mode function
Zach Lysobey
Function.caller tampoco funcionó para mí en modo estricto. Además, según MDN , function.caller no es estándar y no debe usarse en producción. Sin embargo, podría funcionar para la depuración.
jkdev
No tuve ningún problema con el no estándar si funcionaba en Node, pero simplemente no está permitido en modo estricto (lo probé en el nodo 6.10). Lo mismo se aplica para los 'argumentos'. Recibo un mensaje de error: '' 'llamador' y 'argumentos' son propiedades de función restringidas y no se puede acceder en este contexto. "
Tom
21

Yo haría esto:

function Hello() {
  console.trace();
}
inorganik
fuente
¡Esto funciona muy bien! debería ser aceptado como la respuesta correcta, ya que otras formas son viejas \ ya no funcionan
Yuval Pruss
19
function Hello() {
    alert(Hello.caller);
}
Sombra2531
fuente
1
Y solo para el nombre de la función use Hello.caller.name
vanval
igual quearguments.callee.caller.toString()
user2720864
Esta debería ser la respuesta correcta, al menos para 2016
Daniel
Esto no está en una pista estándar, pero funcionará a partir de ECMAScript 5.
Obinna Nwakwue
1
@Daniel: no, no debería. Ver developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Dan Dascalescu el
18

Es más seguro de usar *arguments.callee.callerya que arguments.callerestá en desuso ...

Pablo Cabrera
fuente
36
arguments.calleetambién está en desuso en ES5 y se elimina en modo estricto.
nyuszika7h
2
¿Hay una alternativa? Editar: arguments.calleefue una mala solución a un problema que ahora se ha resuelto mejor developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Matthew
16

Parece que esta es una pregunta bastante resuelta, pero recientemente descubrí que la persona que llama no está permitida en 'modo estricto', así que para mi propio uso escribí una clase que obtendrá la ruta desde donde se llama. Es parte de una pequeña biblioteca auxiliar y, si desea utilizar el código de forma independiente, cambie el desplazamiento utilizado para devolver el seguimiento de la pila de la persona que llama (use 1 en lugar 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
fuente
No funciona para mí function a(){ function b(){ function c(){ return ScriptPath(); } return c(); } return b(); } a()en la consola (no lo he probado en un archivo), pero parece tener una idea razonable. Debería ser votado de todos modos para mayor visibilidad.
ninjagecko
La idea es genial. Estoy analizando de manera diferente, pero en las aplicaciones nw.js, esta es realmente la única idea que da lo que estoy buscando.
Andrew Grothe
Proporcione un ejemplo de cómo llamar a esta función.
pd_au
11

Solo consola registre su pila de errores. Entonces puedes saber cómo te llaman

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

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

sello()

Prasanna
fuente
10

Tanto en ES6 como en modo estricto, use lo siguiente para obtener la función de llamada

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

Tenga en cuenta que, la línea anterior arrojará una excepción, si no hay llamador o no hay una pila anterior. Usar en consecuencia.

VanagaS
fuente
Para obtener llamadas (el nombre de la función actual), use: console.log((new Error()).stack.split("\n")[1].trim().split(" ")[1])
VanagaS
10

Actualización 2018

callerestá prohibido en modo estricto . Aquí hay una alternativa usando la Errorpila (no estándar) .

La siguiente función parece hacer el trabajo en Firefox 52 y Chrome 61-71, aunque su implementación hace muchas suposiciones sobre el formato de registro de los dos navegadores y debe usarse con precaución, dado que arroja una excepción y posiblemente ejecuta dos expresiones regulares. coincidencias antes de hacerse.

'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
fuente
44
Eso es increíble y también horrible.
Ian
Recibí "foo llamado con: hola allí", pero foo no fue llamado con "hola allí", el registro fue llamado con "hola allí"
AndrewR
Correcto, había un "modificador fuera de lugar" en la gramática del mensaje de error. Significaba decir "se llamó a log desde la función f, quería que se imprimiera el mensaje X" pero de la manera más sucinta posible.
Rovanion
7

Quería agregar mi violín aquí para esto:

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

Probé esto es Chrome, Safari e IE (10 y 8). Funciona bien. Solo hay 1 función que importa, así que si te asusta el gran violín, lee a continuación.

Nota: Hay una buena cantidad de mi propio "repetitivo" en este violín. Puede eliminar todo eso y usar divisiones si lo desea. Es solo un "conjunto de funciones ultra seguro" en el que he llegado a confiar.

También hay una plantilla "JSFiddle" que utilizo para muchos violines para simplemente tocar el violín rápidamente.

bladnman
fuente
Me pregunto si podría agregar los "ayudantes" como extensiones para el prototipo en algunos casos, por ejemplo:String.prototype.trim = trim;
autista
6

Si solo desea el nombre de la función y no el código, y desea una solución independiente del navegador, use lo siguiente:

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

Tenga en cuenta que lo anterior devolverá un error si no hay una función de llamada ya que no hay ningún elemento [1] en la matriz. Para evitarlo, use lo siguiente:

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
fuente
1
Esto ha quedado en desuso por muchos años .
Dan Dascalescu
5

Sólo quiero hacerle saber que en PhoneGap / androide del nameno se parece estar funcionando. Pero arguments.callee.caller.toString()hará el truco.

Pablo Armentano
fuente
4

Aquí, todo menos el functionnamees eliminado caller.toString(), con 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
fuente
esto sigue devolviendo la declaración del método completo
Maslow
4

Aquí hay una función para obtener el stacktrace completo :

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

fuente
3

La respuesta de heystewart y la respuesta de JiarongWu mencionaron que el Errorobjeto tiene acceso a stack.

Aquí hay un ejemplo:

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

Los diferentes navegadores muestran la pila en diferentes formatos de cadena:

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)

La mayoría de los navegadores configurarán la pila con var stack = (new Error()).stack. En Internet Explorer, la pila estará indefinida: debe lanzar una excepción real para recuperar la pila.

Conclusión: Es posible determinar que "main" es la persona que llama a "Hello" usando el stacken el Errorobjeto. De hecho, funcionará en los casos en que el enfoque callee/ callerno funcione. También le mostrará el contexto, es decir, el archivo fuente y el número de línea. Sin embargo, se requiere un esfuerzo para hacer que la solución sea multiplataforma.

Stephen Quan
fuente
2

Tenga en cuenta que no puede usar Function.caller en Node.js, use el paquete caller-id en su lugar. Por ejemplo:

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
fuente
1

Prueba el siguiente 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;
}

Trabajó para mí en Firefox-21 y Chromium-25.

Diego Augusto Molina
fuente
Prueba esto para funciones recursivas.
daniel1426
arguments.calleeha quedado en desuso por muchos años .
Dan Dascalescu
1

Otra forma de evitar este problema es simplemente pasar el nombre de la función de llamada como parámetro.

Por ejemplo:

function reformatString(string, callerName) {

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

    return string;
}

Ahora, podría llamar a la función de esta manera:

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

    reformatString(myString, function.name);
}

Mi ejemplo utiliza una verificación codificada del nombre de la función, pero podría usar fácilmente una instrucción de cambio o alguna otra lógica para hacer lo que quiera allí.

GrayedFox
fuente
Creo que esto también resuelve problemas de compatibilidad entre navegadores, en su mayor parte. ¡Pero por favor pruebe esto antes de asumir que es verdad! ( comienza a sudar )
GrayedFox
1

Hasta donde yo sé, tenemos dos formas para esto de fuentes dadas como esta:

  1. argumentos.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;
        }
    }

Creo que tienes tu respuesta :).

Abrar Jahin
fuente
Esto ha quedado en desuso durante muchos años , y Function.caller no funciona en modo estricto.
Dan Dascalescu
1

Por qué todas las soluciones anteriores parecen una ciencia espacial. Mientras tanto, no debería ser más complicado que este fragmento. Todos los créditos a este chico

¿Cómo se encuentra la función de llamada en 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
fuente
3
Esto es lo que obtengo al usar esto: TypeError: no se puede acceder a las propiedades 'caller', 'callee' y 'argumentos' en funciones de modo estricto o en los objetos de argumentos para llamadas a ellas. ¿Alguna idea de cómo trabajar esto en modo estricto?
hard_working_ant
1

Estoy tratando de abordar la pregunta y la recompensa actual con esta pregunta.

La recompensa requiere que la persona que llama se obtenga en modo estricto , y la única forma en que puedo ver esto es refiriéndose a una función declarada fuera del modo estricto.

Por ejemplo, lo siguiente no es estándar, pero se ha probado con versiones anteriores (29/03/2016) y actuales (1 de agosto de 2018) de Chrome, Edge y 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
fuente
Buen truco, pero no funcionará para los módulos ES5, que están completamente en modo estricto .
Dan Dascalescu
0

Si realmente necesita la funcionalidad por alguna razón y desea que sea compatible con todos los navegadores y no se preocupe por cosas estrictas y sea compatible con versiones posteriores, pase esta referencia:

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
fuente
0

Creo que el siguiente código puede 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);
}

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

El registro se ve así:

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
fuente