web-dev-qa-db-fra.com

Comment trouvez-vous la fonction appelant en JavaScript?

function main()
{
   Hello();
}

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

Existe-t-il un moyen de connaître la pile d'appels?

766
Ray Lu
function Hello()
{
    alert("caller is " + Hello.caller);
}

Notez que cette fonctionnalité est non standard, de Function.caller :

Non standard
Cette fonctionnalité n'est pas standard et ne fait pas partie des standards. Ne l'utilisez pas sur les sites de production faisant 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 dans le futur.


Ce qui suit est l'ancienne réponse de 2008, qui n'est plus prise en charge par le Javascript moderne:

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

Trace de la pile

Vous pouvez trouver toute la trace de la pile à l'aide du code spécifique au navigateur. La bonne chose est 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 vraiment très lent d’obtenir la trace de la pile, alors soyez prudent (lisez this } _ pour plus).

  2. Vous devrez définir des noms de fonction pour que la trace de la 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 attendront un nom de fonction juste après le mot clé function et le traiteront comme une fonction anonyme. Pas même Chrome pourra utiliser le nom Klass si vous ne donnez pas le nom kls à la fonction.

    Et en passant, vous pouvez passer à la fonction printStackTrace l'option {guess: true} mais je n'ai trouvé aucune amélioration réelle en procédant de la sorte.

  3. Tous les navigateurs ne vous donnent pas les mêmes informations. C'est-à-dire des paramètres, une colonne de code, etc.


Nom de fonction de l'appelant

À propos, si vous voulez seulement le nom de la fonction appelant (dans la plupart des navigateurs, mais pas dans IE), vous pouvez utiliser:

arguments.callee.caller.name

Mais notez que ce nom sera celui qui suit le mot clé function. 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 (de Pablo Cabrera, Nourdine et Greg Hewgill). La seule chose vraiment sûre et multi-navigateurs que vous pouvez utiliser est:

arguments.callee.caller.toString();

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

145
Mariano Desanze

Vous pouvez obtenir le stacktrace complet:

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

Jusqu'à l'appelant est null.

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

50
ale5000

Pour récapituler (et clarifier) ​​...

ce code: 

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

est équivalent à ceci: 

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

Il est clair que le premier bit est plus portable, car vous pouvez modifier le nom de la fonction, par exemple de "Hello" à "Ciao", tout en maintenant le fonctionnement complet.

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

49
nourdine

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: enter image description here Il suffit de déposer le débogueur à l’endroit où vous souhaitez examiner la pile.

39
Phil

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 que cela limite la longueur de la pile à 10, c'est pourquoi je suis venu à cette page en premier lieu.

(J'utilise ceci pour collecter des piles d'appels dans un constructeur de bas niveau lors de l'exécution, pour afficher et déboguer ultérieurement. La définition d'un point d'arrêt n'est donc pas utile car il sera touché des milliers de fois.)

33
heystewart

Si vous ne voulez 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
25
gumkins

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

Remarques sur obsolete argument.caller: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/caller

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

21
Greg
function Hello() {
    alert(Hello.caller);
}
18
Shadow2531

Il est préférable d'utiliser *arguments.callee.caller puisque arguments.caller est deprecated ...

18
Pablo Cabrera

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 usage personnel, j'ai écrit un cours qui obtiendra le chemin d'accès. C'est une partie d'une petite bibliothèque d'aide et si vous voulez utiliser le code autonome, changez l'offset utilisé pour retourner 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('.');
  };
}
15
QueueHammer

Je voudrais faire ceci:

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

Essayez d'accéder à ceci:

arguments.callee.caller.name
11
user15566

Il suffit de consigner votre pile d’erreur dans le journal. Vous pouvez alors savoir comment on vous appelle

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

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

sello()

10
Prasanna

Je voulais ajouter mon violon ici pour ceci:

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 fonction qui compte, alors si vous êtes effrayé par le grand violon, lisez ci-dessous.

Note: .__ Il y a pas mal de mon propre "passe-partout" dans ce violon. Vous pouvez supprimer tout cela et utiliser des split si vous le souhaitez. C’est juste un ensemble de fonctions ultra-sûres sur lesquelles je m’appuie.

Il existe également un modèle "JSFiddle" que j'utilise dans de nombreux violons pour jouer rapidement du violon.

7
bladnman

Si vous souhaitez simplement le nom de la fonction et non le code et souhaitez une solution indépendante du navigateur, utilisez les éléments suivants:

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

Notez que ce qui précède renvoie une erreur s'il n'y a pas de fonction caller car il n'y a pas d'élément [1] dans le tableau. Pour contourner le 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]);
6
JoolzCheat

Je veux juste vous dire que sur PhoneGap/Android la name ne semble pas fonctionner. Mais arguments.callee.caller.toString() fera l'affaire.

5
Pablo Armentano

Ici, tout sauf la functionname est retiré de 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>
4
BrazFlat

la réponse de heystewart et la réponse de JiarongWu les deux ont mentionné que l'objet Error 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éfinissent la pile avec var stack = (new Error()).stack. Dans Internet Explorer, la pile sera indéfinie - vous devez générer une véritable exception pour récupérer la pile.

Conclusion: Il est possible de déterminer "principal" est l'appelant à "Bonjour" en utilisant la variable stack dans l'objet Error. En fait, cela fonctionnera dans les cas où l'approche callee/caller ne 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 multi-plateforme.

3
Stephen Quan

voici une fonction pour obtenir un stacktrace complet :

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

Mise à jour 2018

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

La fonction suivante semble faire l'affaire dans Firefox 52 et Chrome 61-71, bien que son implémentation suppose beaucoup d'hypothèses sur le format de journalisation des deux navigateurs. 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 with:', ...messages);
    } else {
      console.log(fnName(logLines[2]), 'called with:', ...messages);
    }
  } else {
    console.log(...messages);
  }
}

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

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

3
Rovanion

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 trouvez-vous 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)]
2
Anonymous

Pour autant que je sache, nous avons 2 moyens 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. Fonction.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-vous avoir votre réponse :).

1
Abrar Jahin

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

Travaillé pour moi dans Firefox-21 et Chrome-25.

1

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ève une exception s'il n'y a ni appelant ni pile précédente. Utilisez en conséquence.

1
VanagaS

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

La prime exige que l'appelant soit obtenu en mode strict , et la seule façon de le savoir est de faire référence à une fonction déclarée outside du mode strict.

Par exemple, ce qui suit est non 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();

1
autistic

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 là.

1
GrayedFox

Si vous avez réellement besoin de cette fonctionnalité pour quelque raison que ce soit et que vous souhaitez qu’elle soit compatible avec tous les navigateurs et ne vous inquiétez pas pour les commandes strictes et qu’elle soit compatible avec la transmission directe, transmettez cette 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
Mario PG

Comme aucune des réponses précédentes ne fonctionne comme ce que je cherchais (obtenir uniquement le dernier appelant de fonction et non une fonction de chaîne ou de callstack), je poste ici ma solution pour ceux qui sont comme moi et espèrent que cela fonctionnera pour eux:

function getCallerName(func)
{
  if (!func) return "anonymous";
  let caller = func.caller;
  if (!caller) return "anonymous";
  caller = caller.toString();
  if (!caller.trim().startsWith("function")) return "anonymous";
  return caller.substring(0, caller.indexOf("(")).replace("function","");
}


//  Example of how to use "getCallerName" function

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

function Main(){
Hello();

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

Main();
0
pouyan

Je pense que le 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
0
JiarongWu