web-dev-qa-db-fra.com

Expliquer la syntaxe de la fonction anonyme encapsulée

Sommaire

Pouvez-vous expliquer le raisonnement derrière la syntaxe des fonctions anonymes encapsulées dans JavaScript? Pourquoi cela fonctionne-t-il: (function(){})(); mais cela ne signifie 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 le plaçant entre crochets et en l'exécutant immédiatement:

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

Ceci est utile lors de la création de scripts modularisés pour éviter d'encombrer la portée actuelle ou 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 encadrent le contenu et n'exposent que le résultat (je suis sûr qu'il existe une meilleure façon de le décrire), comme avec (2 + 2) === 4.


Ce que je ne comprend pas

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

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

Pouvez-vous m'expliquer cela?

358
Premasagar

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é en tant que FunctionExpression, et les expressions de fonction peuvent être nommées ou non.

La grammaire de FunctionDeclaration ressemble à ceci:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Et FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Comme vous pouvez le constater, le jeton Identifier (Identifiant opt ) dans FunctionExpression est optionnel. Par conséquent, nous pouvons avoir une expression de fonction sans nom:

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

Ou expression de fonction nommée :

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

Les parenthèses (anciennement appelé l'opérateur de groupement ) ne peuvent entourer que des 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 s'il s'agit d'un FunctionDeclaration ou d'un FunctionExpression, en fonction du contexte où il apparaît.

Dans l'exemple ci-dessus, le second est une expression car le opérateur de virgule ne peut également gérer que des expressions.

Par contre, FunctionDeclarations ne pourrait en réalité apparaître que dans ce que l'on appelle le code "Program", ce qui signifie un code situé en dehors de la portée globale et à l'intérieur de la FunctionBody d'autres fonctions.

Les fonctions à l'intérieur de 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, puisqu'un Block ne peut contenir que des instructions (et que la spécification ECMAScript ne définit aucune instruction de fonction), mais la plupart des implémentations sont tolérantes et prennent simplement la deuxième fonction. , celui qui alerte 'false!'.

Les implémentations de Mozilla - Rhino, SpiderMonkey - ont un comportement différent. Leur grammaire contient une instruction de fonction non standard , ce qui signifie que la fonction sera évaluée à au moment de l'exécution , pas au moment de l'analyse. , comme cela arrive avec FunctionDeclarations. Dans ces implémentations, nous allons définir la première fonction.


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

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

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

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

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

3- Une expression de fonction assignée à la variable multiply :

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

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

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

Même s'il s'agit d'une question et d'une réponse anciennes, elle aborde un sujet qui, à ce jour, laisse de nombreux développeurs en boucle. Je ne peux pas compter le nombre de candidats développeurs JavaScript que j'ai interrogés et qui ne pouvaient pas me dire la différence entre une déclaration de fonction et une expression de fonction et qui n'avait aucune idée de ce qu'est une expression de fonction immédiatement invoquée. .

J'aimerais toutefois mentionner une chose très importante, à savoir 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);
}();

Si cela ne fonctionne pas, c'est que le moteur JavaScript l'interprète comme une déclaration de fonction suivie d'un opérateur de regroupement totalement indépendant qui ne contient aucune expression, et des opérateurs de regroupement doivent contiennent une expression. Selon JavaScript, l’extrait de code ci-dessus est équivalent à l’exemple suivant.

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

();

Une autre chose que je voudrais souligner et qui peut être utile à certaines personnes est que tout identificateur de nom que vous fournissez pour une expression de fonction est quasiment 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 noms avec les définitions de vos fonctions est toujours utile pour déboguer le code, mais c’est une toute autre chose ... :-)

48
natlee75

De bonnes réponses ont déjà été postées. Mais je tiens à noter que les déclarations de fonction retournent un enregistrement d'achèvement vide:

14.1.20 - Sémantique du temps d’exécution: Evaluation

FunctionDeclaration: functionBindingIdentifier(FormalParameters){FunctionBody}

  1. Retour NormalCompletion (vide).

Ce fait n’est pas facile à observer, car la plupart des méthodes permettant d’obtenir la valeur renvoyée convertissent la déclaration de fonction en une expression de fonction. Cependant, eval le montre:

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

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

Mais si vous mettez la fonction entre parenthèses, cela 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(){})().

14
Oriol

En javascript, cela s'appelle Expression de Fonction Immédiatement Invoquée (IIFE) .

Pour en faire une expression de fonction, vous devez:

  1. entourez-le avec ()

  2. place un opérateur vide avant celui-ci

  3. l'assigne à une variable.

Sinon, elle sera traitée 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 invoquer qu'une expression de fonction immédiatement.

Ceci peut être réalisé de différentes manières: Méthode 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 la fonction.

8
asmmahmud

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 à la place de la version la plus répandue:

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

parce que je n'ai pas réussi à faire en sorte que l'indentation fonctionne correctement pour les fichiers javascript dans vim. Il semble que vim n'aime pas les accolades dans une parenthèse ouverte.

3
Andrei Bozantan

Ils peuvent être utilisés avec paramètres-arguments tels que

var x = 3; 
var y = 4;

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

aboutirait à 7

0
Jude

Ces parenthèses supplémentaires créent des fonctions anonymes supplémentaires entre l’espace de nom 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 nom de la fonction parente qui les contient. Comme il y a un objet supplémentaire (fonction anonyme) entre la portée globale et la portée du code, elle n'est pas conservée.

0
Jarkko Hietala

Peut-être que la réponse plus courte serait que

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

est un fonction littérale qui définit une fonction (anonyme). Un couple supplémentaire (), interprété comme une expression, n'est pas attendu au niveau supérieur, mais uniquement de littéraux.

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

se trouve dans une expression qui appelle une fonction anonyme.

0
theking2

Vous pouvez aussi l'utiliser comme:

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

ou

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

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

0
Csaba K.
(function(){
     alert(2 + 2);
 })();

La syntaxe ci-dessus est valable 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 Java l'analyseur syntaxique de script 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.

0
Vithy