Expliquer la syntaxe de la fonction anonyme encapsulée

373

Sommaire

Pouvez-vous expliquer le raisonnement derrière la syntaxe des fonctions anonymes encapsulées en JavaScript? Pourquoi ça marche: (function(){})();mais ça ne marche pas function(){}();:?


Ce que je sais

En JavaScript, on crée une fonction nommée comme ceci:

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

Vous pouvez également créer une fonction anonyme et l'affecter à une variable:

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

Vous pouvez encapsuler un bloc de code en créant une fonction anonyme, puis en l'encapsulant entre crochets et en l'exécutant immédiatement:

(function(){
    alert(2 + 2);
})();

Cela est utile lors de la création de scripts modularisés, pour éviter d'encombrer la portée actuelle ou la portée globale, avec des variables potentiellement conflictuelles - comme dans le cas des scripts Greasemonkey, des plugins jQuery, etc.

Maintenant, je comprends pourquoi cela fonctionne. Les crochets entourent le contenu et exposent uniquement le résultat (je suis sûr qu'il existe une meilleure façon de décrire cela), comme avec (2 + 2) === 4.


Ce que je ne comprends pas

Mais je ne comprends pas pourquoi cela ne fonctionne pas aussi bien:

function(){
    alert(2 + 2);
}();

Pouvez-vous me l'expliquer?

Premasagar
la source
39
Je pense que toutes ces notations variées et ces façons de définir / définir / appeler des fonctions sont la partie la plus déroutante du travail initial avec javascript. Les gens ont tendance à ne pas en parler non plus. Ce n'est pas un point souligné dans les guides ou les blogs. Cela m'épate parce que c'est quelque chose de déroutant pour la plupart des gens, et les gens qui parlent couramment js doivent également l'avoir vécu. C'est comme cette réalité taboue vide dont on ne parle jamais.
ahnbizcad
1
Lisez également le but de cette construction , ou consultez une explication ( technique ) (également ici ). Pour le placement des parenthèses, consultez cette question sur leur emplacement .
Bergi
OT: Pour ceux qui veulent savoir où ces fonctions anonymes sont beaucoup utilisées, veuillez lire de manière adéquategood.com
JavaScript-Module-Pattern-In-Depth.html
Il s'agit d'un cas typique d'expressions de fonction immédiatement appelées (IIFE).
Aminu Kano

Réponses:

410

Cela ne fonctionne pas car il est analysé comme un FunctionDeclaration, et l'identifiant de nom des déclarations de fonction est obligatoire .

Lorsque vous l'entourez de parenthèses, il est évalué comme un FunctionExpression, et les expressions de fonction peuvent être nommées ou non.

La grammaire d'un FunctionDeclarationressemble à ceci:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Et FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Comme vous pouvez le voir, le jeton Identifier(Identifier opt ) FunctionExpressionest facultatif, nous pouvons donc avoir une expression de fonction sans nom défini:

(function () {
    alert(2 + 2);
}());

Ou expression de fonction nommée :

(function foo() {
    alert(2 + 2);
}());

Les parenthèses (officiellement appelé l'opérateur de regroupement ) ne peuvent entourer que les expressions et une expression de fonction est évaluée.

Les deux productions grammaticales peuvent être ambiguës, et elles peuvent être exactement identiques, par exemple:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

L'analyseur sait si c'est un FunctionDeclarationou un FunctionExpression, selon le contexte où il apparaît.

Dans l'exemple ci-dessus, le second est une expression car l' opérateur Virgule peut également gérer uniquement les expressions.

D'un autre côté, les FunctionDeclarations ne peuvent en fait apparaître que dans ce qu'on appelle le Programcode " ", ce qui signifie du code à l'extérieur dans la portée globale et à l'intérieur FunctionBodydes autres fonctions.

Les fonctions à l'intérieur des blocs doivent être évitées, car elles peuvent entraîner un comportement imprévisible, par exemple:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

Le code ci-dessus devrait en fait produire un SyntaxError, car a Blockne peut contenir que des instructions (et la spécification ECMAScript ne définit aucune instruction de fonction), mais la plupart des implémentations sont tolérantes et prendront simplement la deuxième fonction, celle qui alerte 'false!'.

Les implémentations de Mozilla -Rhino, SpiderMonkey, - ont un comportement différent. Leur grammaire contient un énoncé de fonction non standard , ce qui signifie que la fonction sera évaluée au moment de l' exécution , et non au moment de l'analyse, comme cela se produit avec FunctionDeclarations. Dans ces implémentations, nous obtiendrons la première fonction définie.


Les fonctions peuvent être déclarées de différentes manières, comparez les éléments suivants :

1- Une fonction définie avec le constructeur Function affecté à la variable multiply :

var multiply = new Function("x", "y", "return x * y;");

2- Une déclaration de fonction d'une fonction nommée multiplier :

function multiply(x, y) {
    return x * y;
}

3- Une expression de fonction affectée à la variable multiplier :

var multiply = function (x, y) {
    return x * y;
};

4- Une expression de fonction nommée func_name , affectée à la variable multiplier :

var multiply = function func_name(x, y) {
    return x * y;
};
CMS
la source
2
La réponse de CMS est correcte. Pour une excellente explication approfondie des déclarations et expressions de fonction, consultez cet article de kangax .
Tim Down
C'est une excellente réponse. Il semble être intimement lié à la façon dont le texte source est analysé et à la structure du BNF. dans votre exemple 3, dois-je dire qu'il s'agit d'une expression de fonction car elle suit un signe égal, alors que ce formulaire est une déclaration / instruction de fonction lorsqu'il apparaît sur une ligne par lui-même? Je me demande quel serait le but de cela - est-il simplement interprété comme une déclaration de fonction nommée, mais sans nom? À quoi cela sert-il si vous ne l'assignez pas à une variable, ne la nommez pas ou ne l'appelez pas?
Breton
1
Aha. Très utile. Merci, CMS. Cette partie des documents Mozilla auxquels vous avez lié est particulièrement éclairante: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar
1
+1, bien que vous ayez placé le crochet de fermeture dans la mauvaise position dans l'expression de la fonction :-)
NickFitz
1
@GovindRai, Non. Les déclarations de fonctions sont gérées au moment de la compilation et une déclaration de fonction en double remplace la déclaration précédente. Au moment de l'exécution, la déclaration de fonction est déjà disponible et dans ce cas, celle qui est disponible est celle qui alerte "false". Pour plus d'informations, lisez que vous ne connaissez pas JS
Saurabh Misra
50

Même s'il s'agit d'une vieille question et réponse, il aborde un sujet qui, à ce jour, incite de nombreux développeurs à boucler. Je ne peux pas compter le nombre de candidats développeurs JavaScript que j'ai interviewés qui ne pouvaient pas me faire la différence entre une déclaration de fonction et une expression de fonction et qui n'avaient aucune idée de ce qu'est une expression de fonction immédiatement invoquée.

Je voudrais mentionner, cependant, une chose très importante qui est que l'extrait de code de Premasagar ne fonctionnerait pas même s'il lui avait donné un identifiant de nom.

function someName() {
    alert(2 + 2);
}();

La raison pour laquelle cela ne fonctionnerait pas est que le moteur JavaScript l'interprète comme une déclaration de fonction suivie d'un opérateur de regroupement complètement indépendant qui ne contient aucune expression, et les opérateurs de regroupement doivent contenir une expression. Selon JavaScript, l'extrait de code ci-dessus est équivalent au suivant.

function someName() {
    alert(2 + 2);
}

();

Une autre chose que je voudrais souligner qui peut être utile à certaines personnes est que tout identifiant de nom que vous fournissez pour une expression de fonction est à peu près inutile dans le contexte du code, sauf dans la définition de la fonction elle-même.

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

Bien sûr, utiliser des identifiants de nom avec vos définitions de fonction est toujours utile quand il s'agit de déboguer du code, mais c'est autre chose entièrement ... :-)

natlee75
la source
17

D'excellentes réponses ont déjà été publiées. Mais je tiens à noter que les déclarations de fonction renvoient un enregistrement d'achèvement vide:

14.1.20 - Sémantique d'exécution: évaluation

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Renvoie NormalCompletion (vide).

Ce fait n'est pas facile à observer, car la plupart des façons de tenter d'obtenir la valeur renvoyée convertiront la déclaration de fonction en une expression de fonction. Cependant, le evalmontre:

var r = eval("function f(){}");
console.log(r); // undefined

Appeler un enregistrement d'achèvement vide n'a aucun sens. Voilà pourquoi ça function f(){}()ne marche pas. En fait, le moteur JS n'essaie même pas de l'appeler, les parenthèses sont considérées comme faisant partie d'une autre instruction.

Mais si vous encapsulez la fonction entre parenthèses, elle devient une expression de fonction:

var r = eval("(function f(){})");
console.log(r); // function f(){}

Les expressions de fonction renvoient un objet fonction. Et par conséquent , vous pouvez l' appeler: (function f(){})().

Oriol
la source
Dommage que cette réponse soit négligée. Bien qu'elle ne soit pas aussi complète que la réponse acceptée, elle fournit des informations supplémentaires très utiles et mérite plus de votes
Avrohom Yisroel
10

En javascript, cela s'appelle l' expression de fonction immédiatement invoquée (IIFE) .

Pour en faire une expression de fonction, vous devez:

  1. enfermez-le en utilisant ()

  2. placer un opérateur vide devant lui

  3. l'assigner à une variable.

Sinon, il sera traité comme une définition de fonction et vous ne pourrez pas l'appeler / l'invoquer en même temps de la manière suivante:

 function (arg1) { console.log(arg1) }(); 

Ce qui précède vous donnera une erreur. Parce que vous ne pouvez appeler qu'une expression de fonction immédiatement.

Ceci peut être réalisé de deux manières: Voie 1:

(function(arg1, arg2){
//some code
})(var1, var2);

Voie 2:

(function(arg1, arg2){
//some code
}(var1, var2));

Voie 3:

void function(arg1, arg2){
//some code
}(var1, var2);

voie 4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

Tout ce qui précède invoquera immédiatement l'expression de fonction.

asmmahmud
la source
3

J'ai juste une autre petite remarque. Votre code fonctionnera avec un petit changement:

var x = function(){
    alert(2 + 2);
}();

J'utilise la syntaxe ci-dessus au lieu de la version la plus répandue:

var module = (function(){
    alert(2 + 2);
})();

car je n'ai pas réussi à faire fonctionner correctement l'indentation pour les fichiers javascript dans vim. Il semble que vim n'aime pas les accolades à l'intérieur des parenthèses ouvertes.

Andrei Bozantan
la source
Alors, pourquoi cette syntaxe fonctionne-t-elle lorsque vous affectez le résultat exécuté à une variable, mais pas de manière autonome?
paislee
1
@paislee - Parce que le moteur JavaScript interprète toute instruction JavaScript valide commençant par le functionmot - clé comme une déclaration de fonction, auquel cas la fin ()est interprétée comme un opérateur de regroupement qui, selon les règles de syntaxe JavaScript, peut uniquement et doit contenir une expression JavaScript.
natlee75
1
@bosonix - Votre syntaxe préférée fonctionne bien, mais c'est une bonne idée d'utiliser soit la "version plus répandue" que vous avez référencée, soit la variante qui ()est incluse dans l'opérateur de regroupement (celle que Douglas Crockford recommande fortement) pour la cohérence: c'est Il est courant d'utiliser les IIFE sans les affecter à une variable, et il est facile d'oublier d'inclure ces parenthèses d'habillage si vous ne les utilisez pas de manière cohérente.
natlee75
0

La réponse la plus courte serait peut-être que

function() { alert( 2 + 2 ); }

est un littéral de fonction qui définit une fonction (anonyme). Une paire supplémentaire (), qui est interprétée comme une expression, n'est pas attendue au niveau supérieur, uniquement les littéraux.

(function() { alert( 2 + 2 ); })();

se trouve dans une instruction d'expression qui appelle une fonction anonyme.

theking2
la source
0
(function(){
     alert(2 + 2);
 })();

La syntaxe ci-dessus est valide car tout ce qui est passé entre parenthèses est considéré comme une expression de fonction.

function(){
    alert(2 + 2);
}();

La syntaxe ci-dessus n'est pas valide. Parce que l'analyseur de syntaxe du script java recherche le nom de la fonction après le mot-clé de la fonction, car il ne trouve rien, il génère une erreur.

Vithy
la source
2
Bien que votre réponse ne soit pas incorrecte, la réponse acceptée couvre déjà tout cela dans le département.
Jusqu'à Arnold
0

Vous pouvez également l'utiliser comme:

! function() { console.log('yeah') }()

ou

!! function() { console.log('yeah') }()

!- negation op convertit la définition de fn en expression de fn, vous pouvez donc l'invoquer immédiatement avec (). Identique à l'utilisation de 0,fn defouvoid fn def

Csaba K.
la source
-1

Ils peuvent être utilisés avec des paramètres-arguments comme

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

résulterait en 7

Jude
la source
-1

Ces parenthèses supplémentaires créent des fonctions anonymes supplémentaires entre l'espace de noms global et la fonction anonyme qui contient le code. Et en Javascript, les fonctions déclarées dans d'autres fonctions ne peuvent accéder qu'à l'espace de noms de la fonction parent qui les contient. Comme il existe un objet supplémentaire (fonction anonyme) entre la portée globale et la portée du code réel n'est pas conservé.

Jarkko Hietala
la source