Comment découvrir la fonction appelant en JavaScript?


866
function main()
{
   Hello();
}

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

Existe-t-il un moyen de découvrir la pile d'appels?


63
J'espère que c'est juste pour vous aider dans le débogage. Un comportement différent en fonction de l'appelant est une mauvaise idée.
JO.

Quand cela serait-il utile pour le débogage?
Anderson Green

33
@AndersonGreen lorsque vous avez, par exemple, une méthode de rendu de modèle par défaut et voyez qu'elle est appelée deux fois. Plutôt que de passer au peigne fin des milliers de LoC ou de passer au travers du débogueur, vous pouvez simplement voir quelle était la pile à l'époque.
tkone

28
pour voir la trace de la pile, utilisez console.trace () pour chrome. je ne sais pas pour les autres cependant
lukas.pukenis

5
pourquoi est-ce une mauvaise idee?
Jacob Schneider

Réponses:


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

Notez que cette fonctionnalité n'est pas standard , à partir de Function.caller:

Non standard
Cette fonctionnalité n'est pas standard et n'est pas sur une piste standard. Ne l'utilisez pas sur des sites de production face au Web: cela ne fonctionnera pas pour tous les utilisateurs. Il peut également y avoir de grandes incompatibilités entre les implémentations et le comportement peut changer à l'avenir.


Voici l'ancienne réponse de 2008, qui n'est plus prise en charge dans Javascript moderne:

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

254
arguments.callee.caller.nameobtiendra le nom de la fonction.
Rocket Hazmat

137
"Les propriétés 'caller', 'callee' et 'arguments' ne sont pas accessibles sur les fonctions en mode strict ou les objets arguments pour les appeler" - elles sont obsolètes dans ES5 et supprimées en mode strict.
ThatGuy

12
Cela ne fonctionnera que si vous n'utilisez pas le mode strict. La suppression 'use strict';pourrait donc être utile.
pvorb

23
argumentsPEUT être accessible à partir d'une fonction en mode strict, il serait stupide de le déprécier. tout simplement pas de function.arguments de l'extérieur. De plus, si vous avez un argument nommé, sa forme arguments [i] ne suivra pas les modifications que vous apportez à la version nommée dans la fonction.
rvr_jon

41
Cette méthode est devenue obsolète depuis que cet article a été répertorié en 2011. La méthode préférée est désormais Function.caller, (à partir de 2015).
Greg

152

Trace de la pile

Vous pouvez trouver l'intégralité de la trace de la pile à l'aide d'un code spécifique au navigateur. La bonne chose est que quelqu'un l'a déjà fait ; voici le code du projet sur GitHub .

Mais toutes les nouvelles ne sont pas bonnes:

  1. Il est très lent d'obtenir la trace de la pile, alors soyez prudent (lisez ceci pour en savoir plus).

  2. Vous devrez définir des noms de fonction pour que la trace de pile soit lisible. Parce que si vous avez un code comme celui-ci:

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

    Google Chrome alertera ... kls.Hello ( ...mais la plupart des navigateurs s'attendront à un nom de fonction juste après le mot-clé functionet le traiteront comme une fonction anonyme. Un même Chrome ne pourra pas utiliser le Klassnom si vous ne donnez pas le nom klsà la fonction.

    Et au fait, vous pouvez passer à la fonction printStackTrace l'option {guess: true}mais je n'ai trouvé aucune amélioration réelle en faisant cela.

  3. Tous les navigateurs ne vous donnent pas les mêmes informations. Autrement dit, les paramètres, la colonne de code, etc.


Nom de la fonction de l'appelant

Soit dit en passant, si vous ne voulez que le nom de la fonction appelant (dans la plupart des navigateurs, mais pas IE), vous pouvez utiliser:

arguments.callee.caller.name

Mais notez que ce nom sera celui après le functionmot - clé. Je n'ai trouvé aucun moyen (même sur Google Chrome) d'obtenir plus que cela sans obtenir le code de la fonction entière.


Code de fonction de l'appelant

Et résumant le reste des meilleures réponses (par Pablo Cabrera, nourdine et Greg Hewgill). La seule chose multi-navigateur et vraiment sûre que vous pouvez utiliser est:

arguments.callee.caller.toString();

Qui affichera le code de la fonction appelant. Malheureusement, cela ne me suffit pas, et c'est pourquoi je vous donne des conseils pour le StackTrace et le nom de la fonction d'appel (bien qu'ils ne soient pas multi-navigateurs).


1
vous devriez peut-être ajouter Function.callerselon la réponse de @ Greg
Zach Lysobey

Function.callerne fonctionnera pas en mode strict, cependant.
Rickard Elimää

54

Je sais que vous avez mentionné "en Javascript", mais si le but est le débogage, je pense qu'il est plus facile d'utiliser simplement les outils de développement de votre navigateur. Voici à quoi cela ressemble dans Chrome: entrez la description de l'image ici déposez simplement le débogueur à l'endroit où vous souhaitez enquêter sur la pile.


3
C'est une vieille question ... mais c'est certainement la façon la plus moderne de le faire aujourd'hui.
markstewie

53

Pour récapituler (et le rendre plus clair) ...

ce code:

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

est équivalent à ceci:

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

De toute évidence, le premier bit est plus portable, car vous pouvez changer le nom de la fonction, par exemple de "Bonjour" à "Ciao", et toujours faire fonctionner le tout.

Dans ce dernier cas, si vous décidez de refactoriser le nom de la fonction invoquée (Bonjour), vous devrez modifier toutes ses occurrences :(


7
arguments.callee.caller toujours null sur Chrome 25.0.1364.5 dev
Kokizzu

53

Vous pouvez obtenir la trace de pile complète:

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

Jusqu'à ce que l'appelant soit null.

Remarque: cela provoque une boucle infinie sur les fonctions récursives.


2
Désolé pour la réponse tardive, mais je n'ai jamais vu votre commentaire auparavant; seulement pour le cas de récursivité, cela ne fonctionne pas, dans d'autres cas, cela devrait fonctionner.
ale5000

45

J'utilise habituellement (new Error()).stack dans Chrome. La bonne chose est que cela vous donne également les numéros de ligne où l'appelant a appelé la fonction. L'inconvénient est qu'il limite la longueur de la pile à 10, c'est pourquoi je suis venu sur cette page en premier lieu.

(J'utilise cela pour collecter des piles d'appels dans un constructeur de bas niveau pendant l'exécution, pour afficher et déboguer plus tard, donc la définition d'un point d'arrêt n'est pas utile car il sera atteint des milliers de fois)


Pourriez-vous s'il vous plaît ajouter un peu plus de description sur l'explication que vous fournissez?
abarisone

6
C'est la seule chose que je pouvais faire au travail une fois 'use strict';en place. M'a donné les informations dont j'avais besoin - merci!
Jeremy Harris

5
Concernant la limite de longueur de pile ... vous pouvez changer cela avec "Error.stackTraceLimit = Infinity".
Tom

(nouvelle erreur ("StackLog")). stack.split ("\ n") rend la lecture plus agréable.
Teoman shipahi

36

Si vous n'allez pas l'exécuter dans IE <11, alors console.trace () conviendrait.

function main() {
    Hello();
}

function Hello() {
    console.trace()
}

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

Ça marche! Devrait être ajouté plus de votes
positifs

22

Vous pouvez utiliser Function.Caller pour obtenir la fonction appelante. L'ancienne méthode utilisant argument.caller est considérée comme obsolète.

Le code suivant illustre son utilisation:

function Hello() { return Hello.caller;}

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

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

Notes sur l'argument.caller obsolète: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/caller

Soyez conscient que Function.caller n'est pas standard: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/caller


1
C'est la bonne réponse de nos jours. Vous ne pouvez plus faire de trucs arguments.caller.callee. Je souhaite que nous puissions faire passer cela au sommet, car toutes les autres choses sont obsolètes maintenant.
coblr

4
Semble que ce n'est pas possible en mode strict? Cannot access caller property of a strict mode function
Zach Lysobey

Function.caller ne fonctionnait pas non plus pour moi en mode strict. De plus, selon MDN , function.caller n'est pas standard et ne doit pas être utilisé en production. Cela pourrait fonctionner pour le débogage, cependant.
jkdev

Je n'ai eu aucun problème avec le non standard s'il fonctionnait dans Node, mais ce n'est tout simplement pas autorisé en mode strict (j'ai testé sur le nœud 6.10). Il en va de même pour les «arguments». Je reçois un message d'erreur: '' 'appelant' et 'arguments' sont des propriétés de fonction restreintes et ne sont pas accessibles dans ce contexte. "
Tom

21

Je ferais ceci:

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

Cela fonctionne très bien! devrait être accepté comme la bonne réponse, car d'autres méthodes sont anciennes \ ne fonctionnent plus
Yuval Pruss



16

On dirait que c'est une question assez résolue, mais j'ai récemment découvert que l' appelé n'est pas autorisé en «mode strict», donc pour mon propre usage, j'ai écrit une classe qui obtiendra le chemin d'où elle s'appelle. Cela fait partie d'une petite bibliothèque d'aide et si vous souhaitez utiliser le code de manière autonome, changez l'offset utilisé pour renvoyer la trace de pile de l'appelant (utilisez 1 au lieu 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('.');
  };
}

Ne fonctionne pas pour moi avec function a(){ function b(){ function c(){ return ScriptPath(); } return c(); } return b(); } a()dans la console (je n'ai pas essayé dans un fichier), mais semble avoir une idée raisonnable. Doit être voté de toute façon pour la visibilité.
ninjagecko

L'idée est géniale. J'analyse différemment mais dans les applications nw.js, c'est vraiment la seule idée qui donne ce que je cherche.
Andrew Grothe

Veuillez fournir un exemple de la façon d'appeler cette fonction.
pd_au


11

Consignez simplement votre console dans la pile des erreurs. Vous pouvez alors savoir comment vous appelez

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

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

sello()


10

En mode ES6 et strict, utilisez ce qui suit pour obtenir la fonction Appelant

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

Veuillez noter que la ligne ci-dessus lèvera une exception s'il n'y a aucun appelant ou aucune pile précédente. Utilisez en conséquence.


Pour obtenir l'appel (le nom de la fonction actuelle), utilisez: console.log((new Error()).stack.split("\n")[1].trim().split(" ")[1])
VanagaS

10

Mise à jour 2018

callerest interdit en mode strict . Voici une alternative utilisant la pile (non standard)Error .

La fonction suivante semble faire le travail dans Firefox 52 et Chrome 61-71 bien que son implémentation fasse beaucoup d'hypothèses sur le format de journalisation des deux navigateurs et doit être utilisée avec prudence, car elle lève une exception et exécute éventuellement deux expressions régulières appariements avant d'être fait.

'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
C'est incroyable et aussi horrible.
Ian

J'ai reçu "foo appelé avec: hi there", mais foo n'a pas été appelé avec "hi there", le journal a été appelé avec "hi there"
AndrewR

À droite, il y avait un "modificateur mal placé" dans la grammaire du message d'erreur. Cela voulait dire "log a été appelé à partir de la fonction f, il voulait que le message X soit imprimé" mais de la manière la plus succincte possible.
Rovanion

7

Je voulais ajouter mon violon ici pour cela:

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

J'ai testé c'est chrome, safari et IE (10 et 8). Fonctionne bien. Il n'y a qu'une seule fonction qui compte, donc si vous avez peur du gros violon, lisez ci-dessous.

Remarque: il y a une bonne quantité de mon propre "passe-partout" dans ce violon. Vous pouvez supprimer tout cela et utiliser des scissions si vous le souhaitez. C'est juste un ensemble de fonctions ultra-sûres sur lesquelles je viens de compter.

Il y a aussi un modèle "JSFiddle" que j'utilise pour de nombreux violons pour un simple violon rapide.


Je me demande si vous pourriez ajouter les "helpers" comme extensions pour le prototype dans certains cas, par exemple:String.prototype.trim = trim;
autiste

6

Si vous souhaitez simplement le nom de la fonction et non le code, et souhaitez une solution indépendante du navigateur, utilisez ce qui suit:

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

Notez que ce qui précède retournera une erreur s'il n'y a pas de fonction d'appelant car il n'y a pas d'élément [1] dans le tableau. Pour contourner ce problème, utilisez ce qui suit:

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

Je veux juste vous faire savoir que sur PhoneGap / Android du namedoesnt semblent fonctionner. Mais arguments.callee.caller.toString()fera l'affaire.


4

Ici, tout sauf le functionnameest supprimé caller.toString(), avec 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>

cela renvoie toujours la déclaration de méthode entière
Maslow

4

voici une fonction pour obtenir le stacktrace complet :

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

3

La réponse de heystewart et la réponse de JiarongWu ont toutes deux mentionné que l' Errorobjet a accès à la stack.

Voici un exemple:

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

Différents navigateurs affichent la pile dans différents formats de chaîne:

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 plupart des navigateurs définiront la pile avec var stack = (new Error()).stack . Dans Internet Explorer, la pile ne sera pas définie - vous devez lever une véritable exception pour récupérer la pile.

Conclusion: Il est possible de déterminer "principal" est l'appelant à "Bonjour" en utilisant le stackdans l' Errorobjet. En fait, cela fonctionnera dans les cas où l' approche callee/ callerne fonctionne pas. Il vous montrera également le contexte, c'est-à-dire le fichier source et le numéro de ligne. Cependant, des efforts sont nécessaires pour rendre la solution multiplateforme.


2

Notez que vous ne pouvez pas utiliser Function.caller dans Node.js, utilisez plutôt le package id-appelant . Par exemple:

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

Essayez le code suivant:

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

A travaillé pour moi dans Firefox-21 et Chromium-25.


Essayez ceci pour les fonctions récursives.
daniel1426


1

Une autre façon de contourner ce problème consiste à simplement passer le nom de la fonction appelante en tant que paramètre.

Par exemple:

function reformatString(string, callerName) {

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

    return string;
}

Maintenant, vous pouvez appeler la fonction comme ceci:

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

    reformatString(myString, function.name);
}

Mon exemple utilise une vérification codée en dur du nom de la fonction, mais vous pouvez facilement utiliser une instruction switch ou une autre logique pour faire ce que vous voulez.


Je crois que cela résout également les problèmes de compatibilité entre navigateurs, pour la plupart. Mais veuillez tester cela avant de supposer que c'est vrai! ( commence à transpirer )
GrayedFox

1

Pour autant que je sache, nous avons 2 voies pour cela à partir de sources données comme celle-ci-

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

Pensez que vous avez votre réponse :).


Cela est obsolète depuis de nombreuses années et Function.caller ne fonctionne pas en mode strict.
Dan Dascalescu

1

Pourquoi toutes les solutions ci-dessus ressemblent à une science de fusée. En attendant, cela ne devrait pas être plus compliqué que cet extrait. Tous les crédits à ce gars

Comment découvrir la fonction appelant 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)]

3
Voici ce que j'obtiens en utilisant ceci: TypeError: les propriétés 'caller', 'callee' et 'arguments' ne sont pas accessibles sur les fonctions en mode strict ou les objets arguments pour les appeler. Des idées sur la façon de travailler cela en mode strict?
hard_working_ant

1

J'essaie de répondre à la fois à la question et à la prime actuelle avec cette question.

La prime nécessite que l'appelant soit obtenu en mode strict , et la seule façon dont je peux voir cela est de se référer à une fonction déclarée en dehors du mode strict.

Par exemple, ce qui suit n'est pas standard mais a été testé avec les versions précédentes (29/03/2016) et actuelles (1er août 2018) de Chrome, Edge et 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();


Bon hack, mais ne fonctionnera pas pour les modules ES5, qui sont entièrement en mode strict .
Dan Dascalescu

0

Si vous avez vraiment besoin de la fonctionnalité pour une raison quelconque et que vous souhaitez qu'elle soit compatible avec tous les navigateurs et ne vous inquiétez pas pour les choses strictes et la compatibilité ascendante, passez une référence:

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

Je pense que le morceau de code suivant peut être utile:

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

Exécutez le code:

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

Le journal ressemble à ceci:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.