Pourquoi utiliser des expressions de fonctions nommées?

93

Nous avons deux méthodes différentes pour faire une expression de fonction en JavaScript:

Expression de fonction nommée (NFE) :

var boo = function boo () {
  alert(1);
};

Expression de fonction anonyme :

var boo = function () {
  alert(1);
};

Et les deux peuvent être appelés avec boo();. Je ne vois vraiment pas pourquoi / quand je devrais utiliser des fonctions anonymes et quand je devrais utiliser des expressions de fonction nommées. Quelle différence y a-t-il entre eux?

Afshin Mehrabani
la source

Réponses:

86

Dans le cas de l'expression de fonction anonyme, la fonction est anonyme  - littéralement, elle n'a pas de nom. La variable à laquelle vous l'affectez a un nom, mais pas la fonction. (Mise à jour: c'était vrai via ES5. Depuis ES2015 [aka ES6], souvent une fonction créée avec une expression anonyme obtient un vrai nom [mais pas un identifiant automatique], lisez la suite ...)

Les noms sont utiles. Les noms peuvent être vus dans les traces de pile, les piles d'appels, les listes de points d'arrêt, etc. Les noms sont une bonne chose ™.

(Vous deviez vous méfier des expressions de fonction nommées dans les anciennes versions d'IE [IE8 et versions antérieures], car elles créaient par erreur deux objets de fonction complètement séparés à deux moments complètement différents [plus dans mon article de blog Double prise ]. Si vous devez supporte IE8 [!!], il est probablement préférable de s'en tenir aux expressions de fonction anonymes ou aux déclarations de fonction , mais évitez les expressions de fonction nommées.)

Une chose clé à propos d'une expression de fonction nommée est qu'elle crée un identificateur dans la portée avec ce nom pour la fonction dans le corps de la fonction:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

Depuis ES2015, cependant, beaucoup d'expressions de fonction «anonymes» créent des fonctions avec des noms, et cela a été précédé par divers moteurs JavaScript modernes qui étaient assez intelligents pour déduire des noms à partir du contexte. Dans ES2015, votre expression de fonction anonyme entraîne une fonction avec le nom boo. Cependant, même avec la sémantique ES2015 +, l'identifiant automatique n'est pas créé:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

L'affectation du nom de la fonction est effectuée avec l' opération abstraite SetFunctionName utilisée dans diverses opérations de la spécification.

La version courte est essentiellement à chaque fois qu'une expression de fonction anonyme apparaît sur le côté droit de quelque chose comme une affectation ou une initialisation, comme:

var boo = function() { /*...*/ };

(ou cela pourrait être letou constplutôt que var) , ou

var obj = {
    boo: function() { /*...*/ }
};

ou

doSomething({
    boo: function() { /*...*/ }
});

(ces deux derniers sont vraiment la même chose) , la fonction résultante aura un nom ( boo, dans les exemples).

Il existe une exception importante et intentionnelle: l'attribution à une propriété sur un objet existant:

obj.boo = function() { /*...*/ }; // <== Does not get a name

Cela était dû aux problèmes de fuite d'informations soulevés lorsque la nouvelle fonctionnalité était en cours d'ajout; détails dans ma réponse à une autre question ici .

TJ Crowder
la source
1
Il est intéressant de noter qu'il y a au moins deux endroits où l'utilisation des NFE offre encore des avantages concrets: premièrement, pour les fonctions destinées à être utilisées comme constructeurs via l' newopérateur (donner à toutes ces fonctions des noms rend la .constructorpropriété plus utile lors du débogage pour déterminer ce que le diable un objet est une instance de), et pour les littéraux de fonction passés directement dans une fonction sans avoir été préalablement affectés à une propriété ou à une variable (par exemple setTimeout(function () {/*do stuff*/});). Même Chrome les montre à (anonymous function)moins que vous ne les aidiez en les nommant.
Mark Amery
4
@MarkAmery: "est-ce toujours vrai? J'ai ... essayé de CTRL-F pour ces règles et je n'ai pas pu les trouver" Oh oui. :-) Il est éparpillé dans toute la spécification plutôt que d'être au même endroit définissant un ensemble de règles, recherchez simplement "setFunctionName". J'ai ajouté un petit sous-ensemble de liens ci-dessus, mais il apparaît actuellement dans environ 29 endroits différents. Je ne serais que légèrement surpris si votre setTimeoutexemple ne saisissait pas le nom de l'argument formel déclaré poursetTimeout , s'il en avait un. :-) Mais oui, les NFE sont vraiment utiles si vous savez que vous n'aurez pas affaire à d'anciens navigateurs qui en font un hachage.
TJ Crowder
24

La dénomination des fonctions est utile si elles ont besoin de se référencer elles-mêmes (par exemple pour les appels récursifs). En effet, si vous passez une expression de fonction littérale comme argument directement à une autre fonction, cette expression de fonction ne peut pas se référencer directement en mode strict ES5 à moins qu'elle ne soit nommée.

Par exemple, considérez ce code:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

Il serait impossible d'écrire ce code aussi proprement si l'expression de fonction transmise setTimeoutétait anonyme; nous aurions besoin de l'attribuer à une variable à la place avant l' setTimeoutappel. De cette façon, avec une expression de fonction nommée, est légèrement plus courte et plus nette.

Il était historiquement possible d'écrire du code comme celui-ci même en utilisant une expression de fonction anonyme, en exploitant arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... mais arguments.calleeest obsolète, et est carrément interdit en mode strict ES5. C'est pourquoi MDN conseille:

Évitez d'utiliser arguments.callee() en donnant un nom aux expressions de fonction ou en utilisant une déclaration de fonction où une fonction doit s'appeler elle-même.

(c'est moi qui souligne)

Mark Amery
la source
3

Si une fonction est spécifiée en tant qu'expression de fonction, elle peut recevoir un nom.

Il ne sera disponible que dans la fonction (sauf IE8-).

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

Ce nom est destiné à un appel de fonction récursive fiable, même s'il est écrit dans une autre variable.

De plus, le nom NFE (Named Function Expression) PEUT être écrasé par la Object.defineProperty(...)méthode suivante:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

Remarque: cela ne peut pas être fait avec la déclaration de fonction. Ce nom de fonction interne "spécial" est spécifié uniquement dans la syntaxe de l'expression de fonction.

romain
la source
2

Vous devez toujours utiliser des expressions de fonction nommées , c'est pourquoi:

  1. Vous pouvez utiliser le nom de cette fonction lorsque vous avez besoin de récursivité.

  2. Les fonctions anonymes n'aident pas lors du débogage car vous ne pouvez pas voir le nom de la fonction qui pose des problèmes.

  3. Lorsque vous ne nommez pas une fonction, plus tard, il est plus difficile de comprendre ce qu'elle fait. Lui donner un nom le rend plus facile à comprendre.

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

Ici, par exemple, comme la barre de nom est utilisée dans une expression de fonction, elle n'est pas déclarée dans la portée externe. Avec les expressions de fonction nommées, le nom de l'expression de fonction est inclus dans sa propre portée.

Antero Ukkonen
la source
1

Il est préférable d'utiliser des expressions de fonction nommées lorsque vous souhaitez pouvoir référencer la fonction en question sans avoir à vous fier à des fonctionnalités obsolètes telles que arguments.callee.

Sudhir Bastakoti
la source
3
C'est plus un commentaire qu'une réponse. Peut-être
qu'une