Comment obtenir le nom de la fonction à partir de cette fonction?

263

Comment puis-je accéder à un nom de fonction depuis l'intérieur de cette fonction?

// parasitic inheritance
var ns.parent.child = function() {
  var parent = new ns.parent();
  parent.newFunc = function() {

  }
  return parent;
}

var ns.parent = function() {
  // at this point, i want to know who the child is that called the parent
  // ie
}

var obj = new ns.parent.child();
Scott Klarenbach
la source
eh bien, dans le parent, je peux alors accéder à d'autres fonctions par convention, telles que ns [enfant] [schéma] ou ns [enfant] [dbService]. Sans cela, je dois coder en dur ces références dans chaque classe enfant.
Scott Klarenbach
pourquoi ne pas simplement passer la fonction enfant en argument au parent? var parent = new ns.parent (this);
plodder
car il existe des dizaines de ces recherches et des dizaines d'enfants. C'est actuellement ce que je fais, mais c'est la même chose à chaque fois et serait parfait si cette logique en double pouvait simplement être placée une fois dans le parent, en fonction de la fonction dérivée.
Scott Klarenbach
voyez, ce n'est pas la fonction enfant que je veux, c'est la convention de dénomination utilisée, car cette convention de dénomination peut être utilisée pour charger d'autres fonctions qui ne sont pas actuellement définies sur l'objet enfant, mais qui sont liées à cet enfant dans tout le système.
Scott Klarenbach
1
@Scott Je suis d'accord avec tout le monde, votre gestion de votre complexité et de votre structure de code est incorrecte. Ce type de couplage dur est une mauvaise décision de conception et va faire un gâchis. @SimeVidas votre un grand nécromancien :)
Raynos

Réponses:

163

Dans ES5, la meilleure chose à faire est de:

function functionName(fun) {
  var ret = fun.toString();
  ret = ret.substr('function '.length);
  ret = ret.substr(0, ret.indexOf('('));
  return ret;
}

L'utilisation Function.callern'est pas standard. Function.calleret arguments.calleesont tous deux interdits en mode strict.

Edit: la réponse basée sur l'expression rationnelle de nus ci-dessous réalise la même chose, mais a de meilleures performances!

Dans ES6, vous pouvez simplement utiliser myFunction.name.

Remarque: Attention, certains minificateurs JS peuvent jeter les noms de fonction pour mieux compresser; vous devrez peut-être modifier leurs paramètres pour éviter cela.

Vlad A Ionescu
la source
Bien que cela n'ait pas répondu suffisamment à la question pour moi (en raison de fonctions anonymes), cela m'a donné l'idée de le faire: ce fun.toString().substr(0, 100)qui pour mes besoins suffira pour localiser la fonction en question. Alors, merci pour l'inspiration!
Campbeln
3
Yup myFunction.nameest la meilleure chose à faire dans ES6.
Vlad A Ionescu
15
À quoi myFunction.nameça sert si vous tapez le nom de la fonction? défait le but des variables magiques.
adi518
2
@ adi518: pas nécessairement .. supposons que vous ayez un tableau de fonctions et que vous itériez dessus, en utilisant for / foreach .. alors arr[index].namecela fonctionnera aussi :)
Debasish Chowdhury
2
@ adi518 Ce n'est pas inutile. Si je renomme la fonction dans mon IDE, elle myFunction.namesera également mise à jour. Bien sûr, intelliJ prend en charge le changement de nom des identifiants dans les chaînes, mais cela fonctionne beaucoup moins régulièrement et deviendra silencieusement obsolète si quelqu'un oublie de cocher l'option "Rechercher dans les chaînes". myFunction.nameest un choix légèrement meilleur que le codage en dur de la chaîne.
byxor
141

ES6 (inspiré par la réponse de sendy halim ci-dessous):

myFunction.name

Explication sur MDN . À partir de 2015, fonctionne dans nodejs et tous les principaux navigateurs, sauf IE.

Remarque: Sur les fonctions liées, cela donnera " bound <originalName>". Vous devrez supprimer la "borne" si vous souhaitez obtenir le nom d'origine.


ES5 (inspiré par la réponse de Vlad):

Si vous avez une référence à la fonction, vous pouvez faire:

function functionName( func )
{
    // Match:
    // - ^          the beginning of the string
    // - function   the word 'function'
    // - \s+        at least some white space
    // - ([\w\$]+)  capture one or more valid JavaScript identifier characters
    // - \s*        optionally followed by white space (in theory there won't be any here,
    //              so if performance is an issue this can be omitted[1]
    // - \(         followed by an opening brace
    //
    var result = /^function\s+([\w\$]+)\s*\(/.exec( func.toString() )

    return  result  ?  result[ 1 ]  :  '' // for an anonymous function there won't be a match
}
  • Je n'ai pas exécuté de tests unitaires à ce sujet, ni vérifié les différences d'implémentation, mais en principe, cela devrait fonctionner, sinon laisser un commentaire.
  • Remarque: ne fonctionnera pas sur les fonctions liées
  • Remarque: cela calleret calleesont considérés comme obsolètes.

[1] Je l'inclus ici car il est légal et souvent les outils de mise en évidence de la syntaxe ne prennent pas en compte l'espace blanc entre le nom de la fonction et la parenthèse. D'un autre côté, je ne connais aucune implémentation de .toString () qui inclura des espaces blancs ici, c'est pourquoi vous pouvez l'omettre.


En réponse à la question d'origine, je supprimerais l'héritage parasite et opterais pour des modèles de conception OOP plus traditionnels. J'ai écrit un TidBits.OoJs pour écrire confortablement du code OOP en JavaScript avec un ensemble de fonctionnalités imitant le C ++ (pas encore terminé, mais surtout).

Je vois dans les commentaires que vous souhaitez éviter de transmettre les parentbesoins d' informations à son constructeur. Je dois admettre que les modèles de conception traditionnels ne vous sauveront pas de celui-ci, car il est généralement considéré comme une bonne chose de rendre vos dépendances évidentes et appliquées.

Je suggère également de s'éloigner des fonctions anonymes. Ils ne font que le débogage et le profilage d'un PITA parce que tout apparaît simplement comme une "fonction anonyme", et il n'y a aucun avantage pour eux que je sache.


la source
3
Nice RegEx! Voici un test de performance montrant que votre code est le plus rapide dans de nombreux cas. En général, il semble que RegEx bat en moyenne le JS natif d'environ 2x fois ici. jsperf.com/get-function-name/2
Beejor
@Tomasz Bonjour, merci de l'avoir signalé. Je ne le savais pas. Une fonction liée est en fait une nouvelle fonction qui enveloppe votre fonction d'origine. La norme ecmascript § 19.2.3.2 définit que le nom de la nouvelle fonction doit être "lié" + originalName. La méthode toString ne fonctionnera pas non plus sur les fonctions liées ... Vous devrez supprimer le mot lié.
Quel est l'intérêt de myFunction.name si vous tapez le nom de la fonction? bat le but des variables magiques.
Borjovsky
Notez que si vous utilisez React Native, cela peut ne pas fonctionner correctement. J'ai un projet RN sur lequel je travaille avec la version 36 de l'expo et la .namepropriété d' une méthode de composant apparaissait comme la chaîne "valeur", quel que soit son nom. Curieusement, lors des tests dans l'application expo, cela allait, cependant, une fois compilé dans une application réelle, il se bloquait silencieusement.
Andy Corman
64

vous attribuez une fonction sans nom à une variable. vous aurez probablement besoin d'une expression de fonction nommée à la place ( http://kangax.github.com/nfe/ ).

var x = function x() {
    console.log( arguments.callee.name );
}
x();

mais je ne sais pas combien de cross-browser c'est; il y a un problème avec IE6 qui vous fait fuir le nom de la fonction vers la portée externe. aussi, arguments.callee est un peu obsolète et entraînera une erreur si vous utilisez strict mode.

caractère générique
la source
1
Cela ne fonctionne pas sur les anciennes versions du navigateur Safari.
Anubhav Gupta
17

Any constructorexpose une propriété name, qui est le nom de la fonction. Vous accédez à constructorvia une instance (en utilisant new) ou un prototype:

function Person() {
  console.log(this.constructor.name); //Person
}

var p = new Person();
console.log(p.constructor.name); //Person

console.log(Person.prototype.constructor.name);  //Person
Roland
la source
6
Le premier console.logjournal des appels windowou Objectpour moi en fonction de l'endroit où je l'exécute, non Person.
Bart S
Êtes-vous sûr d'avoir instancié en utilisant "nouveau"? Sinon, cela ne peut pas fonctionner.
roland
14

Cela ressemble à la chose la plus stupide que j'ai écrite de ma vie, mais c'est drôle: D

function getName(d){
  const error = new Error();
  const firefoxMatch = (error.stack.split('\n')[0 + d].match(/^.*(?=@)/) || [])[0];
  const chromeMatch = ((((error.stack.split('at ') || [])[1 + d] || '').match(/(^|\.| <| )(.*[^(<])( \()/) || [])[2] || '').split('.').pop();
  const safariMatch = error.stack.split('\n')[0 + d];

  // firefoxMatch ? console.log('firefoxMatch', firefoxMatch) : void 0;
  // chromeMatch ? console.log('chromeMatch', chromeMatch) : void 0;
  // safariMatch ? console.log('safariMatch', safariMatch) : void 0;

  return firefoxMatch || chromeMatch || safariMatch;
}

d- profondeur de pile. 0- retourner ce nom de fonction, 1- parent, etc .;
[0 + d]- juste pour comprendre - ce qui se passe;
firefoxMatch- fonctionne pour le safari, mais j'avais vraiment un peu de temps pour les tests, car le propriétaire de mac était revenu après avoir fumé et m'a chassé: '(

Essai:

function limbo(){
  for(let i = 0; i < 4; i++){
    console.log(getName(i));
  }
}
function lust(){
  limbo();
}
function gluttony(){
  lust();
}

gluttony();

Résultat:
Chrome:
entrez la description de l'image ici

Fitefox:
entrez la description de l'image ici

Cette solution ne créait que pour le plaisir ! Ne l'utilisez pas pour de vrais projets. Cela ne dépend pas de la spécification ES, cela ne dépend que de la réalisation du navigateur. Après la prochaine mise à jour de chrome / firefox / safari, il peut être cassé.
De plus, il n'y a pas de traitement d'erreur (ha) - si la dlongueur de la pile est supérieure - vous obtiendrez une erreur;
Pour les autres modèles de messages d'erreur de wrowsers - vous obtiendrez une erreur;
Cela doit fonctionner pour les classes ES6 ( .split('.').pop()), mais vous pouvez obtenir une erreur;

user3335966
la source
13

Cela pourrait fonctionner pour vous:

function foo() { bar(); }

function bar() { console.log(bar.caller.name); }

l'exécution de foo () affichera "foo" ou indéfini si vous appelez à partir d'une fonction anonyme.

Il fonctionne également avec les constructeurs, auquel cas il afficherait le nom du constructeur appelant (par exemple "Foo").

Plus d'informations ici: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Caller

Ils affirment que ce n'est pas standard, mais aussi qu'il est pris en charge par tous les principaux navigateurs: Firefox, Safari, Chrome, Opera et IE.

Jacob Mouka
la source
Non disponible en mode strict ... mmm
Ka Mok
8

Tu ne peux pas. Les fonctions n'ont pas de noms selon la norme (bien que mozilla ait un tel attribut) - elles ne peuvent être attribuées qu'à des variables avec des noms.

Aussi votre commentaire:

// access fully qualified name (ie "my.namespace.myFunc")

est à l'intérieur de la fonction my.namespace.myFunc.getFn

Ce que vous pouvez faire est de renvoyer le constructeur d'un objet créé par new

Vous pourriez donc dire

var obj = new my.namespace.myFunc();
console.info(obj.constructor); //my.namespace.myFunc
bûcheur
la source
1
j'en ai besoin à l'intérieur du func parce que je le fais passer dans la chaîne d'héritage et j'ai omis ce truc par souci de concision.
Scott Klarenbach du
1
'this' sera l'instance qui a appelé la fonction. Donc, si vous "ceci" à l'intérieur de la fonction parent qui vous donnera l'instance de la fonction qui l'a appelée. C'est tout ce dont vous avez besoin - vous ne savez pas pourquoi vous avez besoin du nom aussi
plodder
1
eh bien, dans le parent, je peux alors accéder à d'autres fonctions par convention, telles que ns [enfant] [schéma] ou ns [enfant] [dbService]. Sans cela, je dois coder en dur ces références dans chaque classe enfant.
Scott Klarenbach
1
mon cas d'utilisation pour trouver le nom est de faciliter la construction de messages console.error qui indiquent d'où vient le message sans avoir à revenir au vidage de la pile. par exemple console.error ({'error': {self.name reference} + "error with input parameter"). Il devient beaucoup plus facile de couper / coller des messages d'erreur qui se répètent dans plusieurs fonctions si vous n'avez pas à vous arrêter et à changer le texte à chaque fois. Dans mon cas, je renvoie des erreurs de promesse à partir de plusieurs appels de promesse - agréable de savoir quelle promesse / fonction a généré l'erreur.
Scott
7

Vous pouvez l'utiliser pour les navigateurs qui prennent en charge Error.stack (pas presque tous, probablement)

function WriteSomeShitOut(){ 
  var a = new Error().stack.match(/at (.*?) /);
  console.log(a[1]);
} 
WriteSomeShitOut();

bien sûr, c'est pour la fonction actuelle, mais vous avez l'idée.

heureux de baver pendant que vous codez

no_ripcord
la source
4

Vous pouvez utiliser la namepropriété pour obtenir le nom de la fonction, sauf si vous utilisez une fonction anonyme

Par exemple:

var Person = function Person () {
  this.someMethod = function () {};
};

Person.prototype.getSomeMethodName = function () {
  return this.someMethod.name;
};

var p = new Person();
// will return "", because someMethod is assigned with anonymous function
console.log(p.getSomeMethodName());

essayons maintenant avec la fonction nommée

var Person = function Person () {
  this.someMethod = function someMethod() {};
};

maintenant vous pouvez utiliser

// will return "someMethod"
p.getSomeMethodName()
sendy halim
la source
4

Vous pouvez utiliser le nom du constructeur comme:

{your_function}.prototype.constructor.name

ce code renvoie simplement le nom d'une méthode.

muhamad zolfaghari
la source
3

dans la mesure où ECMAScript 6vous pouvez utiliser la méthode Function.name

function doSomething() {}

alert(doSomething.name); // alerts "doSomething"
pleerock
la source
Dans ReactJS, vous devez ajouter ceci: console.log (this.doSomething.name);
Bitclaw
2
qui ne fait pas partie de la fonction
Scott
3

Vous pouvez utiliserFunction.name :

Dans la plupart des implémentations de JavaScript, une fois que vous avez la référence de votre constructeur dans la portée, vous pouvez obtenir son nom de chaîne à partir de sa propriété name (par exemple Function.name ou Object.constructor.name

Vous pouvez utiliserFunction.callee :

La arguments.callerméthode native a été déconseillée, mais la plupart des navigateurs prennent en charge Function.caller, qui renverra l'objet invoquant réel (son corps de code): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ Fonction / appelant? Redirectlocale = en-US & redirectslug = JavaScript% 2FReference% 2FGlobal_Objects% 2FFunction% 2Fcaller

Vous pouvez créer une carte source :

Si vous avez besoin de la signature de la fonction littérale (le "nom" de celle-ci) et non de l'objet lui-même, vous devrez peut-être recourir à quelque chose d'un peu plus personnalisé, comme la création d'une référence de tableau des valeurs de chaîne API que vous devrez accéder fréquemment. Vous pouvez les mapper ensemble à l'aide de Object.keys()votre tableau de chaînes, ou consulter la bibliothèque de cartes sources de Mozilla sur GitHub, pour des projets plus importants: https://github.com/mozilla/source-map

Benny
la source
2

Je sais que c'est une vieille question, mais récemment j'ai rencontré un problème similaire en essayant de décorer certaines méthodes de React Component, à des fins de débogage. Comme les gens l'ont déjà dit, arguments.calleret arguments.calleesont interdits en mode strict qui est probablement activé par défaut dans votre transpilage React. Vous pouvez le désactiver, ou j'ai pu trouver un autre hack, car dans React toutes les fonctions de classe sont nommées, vous pouvez réellement faire ceci:

Component.prototype.componentWillMount = function componentWillMount() {
    console.log('Callee name: ', this.__proto__.constructor.toString().substr(0,30));
...
}
frankies
la source
1

Cela a fonctionné pour moi.

function AbstractDomainClass() {
    this.className = function() {
        if (!this.$className) {
            var className = this.constructor.toString();
            className = className.substr('function '.length);
            className = className.substr(0, className.indexOf('('));
            this.$className = className;
        }
        return this.$className;
    }
}

Code de test:

var obj = new AbstractDomainClass();
expect(obj.className()).toBe('AbstractDomainClass');
user1379687
la source
1

J'ai eu un problème similaire et je l'ai résolu comme suit:

Function.prototype.myname = function() {
   return this.toString()
       .substr( 0, this.toString().indexOf( "(" ) )
       .replace( "function ", "" ); 
}

Ce code implémente, d'une manière plus confortable, une réponse que j'ai déjà lue ici en haut de cette discussion. Maintenant, j'ai une fonction membre qui récupère le nom de n'importe quel objet fonction. Voici le script complet ...

<script language="javascript" TYPE="text/javascript">

    Function.prototype.myname = function() { 
        return this.toString()
            .substr( 0, this.toString().indexOf( "(" ) )
            .replace("function ", "" ); 
    }
    function call_this( _fn ) { document.write( _fn.myname() ); }
    function _yeaaahhh() { /* do something */ }
    call_this( _yeaaahhh ); 

</script>
Sandro Rosa
la source
1

Si j'ai compris ce que vous vouliez faire, c'est ce que je fais à l'intérieur d'un constructeur de fonctions.

if (!(this instanceof arguments.callee)) {
    throw "ReferenceError: " + arguments.callee.name + " is not defined";
}
Zibri
la source
2
Remarque: obsolète - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Helcio R. Macedo Pandelo
0

Cela fonctionnera dans ES5, ES6, tous les navigateurs et les fonctions en mode strict.

Voici à quoi cela ressemble avec une fonction nommée.

(function myName() {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at myName (<anonymous>:2:15)

Voici à quoi cela ressemble avec une fonction anonyme.

(() => {
  console.log(new Error().stack.split(/\r\n|\r|\n/g)[1].trim());
})();
at <anonymous>:2:15
Eric
la source
Cela produit des résultats différents par navigateur. Chrome: at myName (<anonymous>:2:15)Firefox:@debugger eval code:3:3
jkdev
(fonction myName () {console.log (new Error (). stack.split (/ \ r \ n | \ r | \ n / g) [1] .trim (). split ("") [1])) ;}) ();
Zibri
-2

regardez ici: http://www.tek-tips.com/viewthread.cfm?qid=1209619

arguments.callee.toString();

semble convenir à vos besoins.

Manuel Bitto
la source
3
arguments.callee.toString () renvoie simplement la source de la fonction.
Scott Klarenbach
2
Non autorisé: 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
Eric Hodonsky
-2

vous pouvez utiliser Error.stack pour tracer le nom de la fonction et la position exacte de l'endroit où vous vous trouvez.

Voir stacktrace.js

kofifus
la source
-3

Un moyen facile d'obtenir le nom de la fonction dans la fonction que vous exécutez.

function x(){alert(this.name)};x()

Dave Brown
la source
1
me donne un GUID
Tamir Daniely
1
être conscient du contenu de thiscelui - ci peut être n'importe quoi. essai(function(){console.log(this.name);}).bind({name: "put basically anything here even an object maybe a 1TB string maybe a class definition"})()
Valen