Erläutern Sie die gekapselte anonyme Funktionssyntax


372

Zusammenfassung

Können Sie die Gründe für die Syntax für gekapselte anonyme Funktionen in JavaScript erläutern? Warum funktioniert das: (function(){})();aber das nicht : function(){}();?


Was ich weiß

In JavaScript erstellt man eine benannte Funktion wie folgt:

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

Sie können auch eine anonyme Funktion erstellen und einer Variablen zuweisen:

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

Sie können einen Codeblock kapseln, indem Sie eine anonyme Funktion erstellen, diese in Klammern setzen und sofort ausführen:

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

Dies ist nützlich, wenn Sie modularisierte Skripts erstellen, um zu vermeiden, dass der aktuelle oder globale Bereich mit möglicherweise widersprüchlichen Variablen überfüllt wird - wie im Fall von Greasemonkey-Skripten, jQuery-Plugins usw.

Jetzt verstehe ich, warum das funktioniert. Die Klammern schließen den Inhalt ein und zeigen nur das Ergebnis an (ich bin sicher, es gibt einen besseren Weg, dies zu beschreiben), z. B. mit (2 + 2) === 4.


Was ich nicht verstehe

Aber ich verstehe nicht, warum das nicht genauso gut funktioniert:

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

Kannst du mir das erklären?


39
Ich denke, all diese unterschiedlichen Notationen und Möglichkeiten zum Definieren / Setzen / Aufrufen von Funktionen sind der verwirrendste Teil der anfänglichen Arbeit mit Javascript. Die Leute neigen auch dazu, nicht über sie zu sprechen. Es ist kein hervorgehobener Punkt in Leitfäden oder Blogs. Es macht mich wahnsinnig, weil es für die meisten Menschen verwirrend ist, und Leute, die fließend js sprechen, müssen es auch durchgemacht haben. Es ist wie diese leere Tabu-Realität, über die nie gesprochen wird.
Ahnbizcad

1
Lesen Sie auch den Zweck dieses Konstrukts oder überprüfen Sie eine ( technische ) Erklärung (auch hier ). Informationen zur Platzierung der Klammern finden Sie in dieser Frage zu ihrer Position .
Bergi

OT: Für diejenigen , die in der diese anonymen Funktionen wissen wollen , eine Menge verwendet werden, lesen Sie bitte adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi

Dies ist ein typischer Fall von sofort aufgerufenen Funktionsausdrücken (IIFE).
Aminu Kano

Antworten:


410

Es funktioniert nicht, weil es als analysiert FunctionDeclarationwird und die Namenskennung von Funktionsdeklarationen obligatorisch ist .

Wenn Sie es mit Klammern umgeben, wird es als a ausgewertet FunctionExpression, und Funktionsausdrücke können benannt werden oder nicht.

Die Grammatik von a FunctionDeclarationsieht so aus:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Und FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Wie Sie sehen können, ist das Token Identifier(Identifier opt ) FunctionExpressionoptional, daher können wir einen Funktionsausdruck ohne definierten Namen haben:

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

Oder benannter Funktionsausdruck:

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

Die Klammern (formal als Gruppierungsoperator bezeichnet ) können nur Ausdrücke umgeben, und ein Funktionsausdruck wird ausgewertet.

Die beiden Grammatikproduktionen können mehrdeutig sein und genau gleich aussehen, zum Beispiel:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Der Parser weiß, ob es sich um ein FunctionDeclarationoder ein handelt FunctionExpression, je nachdem, in welchem Kontext es angezeigt wird.

Im obigen Beispiel ist der zweite ein Ausdruck, da der Komma-Operator auch nur Ausdrücke verarbeiten kann.

Andererseits FunctionDeclarationkönnte s tatsächlich nur im sogenannten " Program" Code erscheinen, dh Code außerhalb des globalen Bereichs und innerhalb FunctionBodyanderer Funktionen.

Funktionen innerhalb von Blöcken sollten vermieden werden, da sie zu einem unvorhersehbaren Verhalten führen können, z.

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

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

Der obige Code sollte tatsächlich a erzeugen SyntaxError , da a Blocknur Anweisungen enthalten kann (und die ECMAScript-Spezifikation keine Funktionsanweisung definiert), die meisten Implementierungen jedoch tolerant sind und einfach die zweite Funktion übernehmen, die alarmiert 'false!'.

Die Mozilla-Implementierungen - Nashorn, SpiderMonkey - haben ein anderes Verhalten. Ihre Grammatik enthält eine nicht standardmäßige Funktionsanweisung, was bedeutet, dass die Funktion bei bewertet wird Laufzeit und nicht zur Analysezeit wird, wie dies bei FunctionDeclarations der . In diesen Implementierungen wird die erste Funktion definiert.


Funktionen können auf verschiedene Arten deklariert werden. Vergleichen Sie Folgendes :

1- Eine Funktion, die mit dem der Variablen multiplizierten Funktionskonstruktor multipliziert wurde :

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

2- Eine Funktionsdeklaration einer Funktion namens multiplizieren :

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

3- Ein der Variablen multiplizierter Funktionsausdruck multiplizieren :

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

4- Ein benannter Funktionsausdruck func_name , der der Variablen multiplizieren zugewiesen ist :

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

2
Die Antwort von CMS ist richtig. Eine ausführliche Erläuterung der Funktionsdeklarationen und -ausdrücke finden Sie in diesem Artikel von kangax .
Tim Down

Dies ist eine großartige Antwort. Es scheint eng mit der Analyse des Quelltextes und der Struktur des BNF verbunden zu sein. Soll ich in Ihrem Beispiel 3 sagen, dass es sich um einen Funktionsausdruck handelt, weil er einem Gleichheitszeichen folgt, wobei diese Form eine Funktionsdeklaration / -anweisung ist, wenn sie in einer eigenen Zeile erscheint? Ich frage mich, was der Zweck davon wäre - wird es nur als benannte Funktionsdeklaration interpretiert, aber ohne Namen? Welchen Zweck hat das, wenn Sie es keiner Variablen zuweisen, benennen oder aufrufen?
Bretonischer

1
Aha. Sehr hilfreich. Danke, CMS. Dieser Teil der Mozilla-Dokumente, auf die Sie verlinkt haben, ist besonders aufschlussreich: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar

1
+1, obwohl Sie die schließende Klammer im Funktionsausdruck an der falschen Position hatten :-)
NickFitz

1
@GovindRai, Nein. Funktionsdeklarationen werden zur Kompilierungszeit verarbeitet, und eine doppelte Funktionsdeklaration überschreibt die vorherige Deklaration. Zur Laufzeit ist die Funktionsdeklaration bereits verfügbar. In diesem Fall ist die verfügbare Deklaration diejenige, die "false" warnt. Für weitere Informationen lesen Sie
Saurabh Misra

50

Obwohl dies eine alte Frage und Antwort ist, wird ein Thema behandelt, das bis heute viele Entwickler für eine Schleife wirft. Ich kann die Anzahl der von mir befragten JavaScript-Entwicklerkandidaten nicht zählen, die mir den Unterschied zwischen einer Funktionsdeklaration und einem Funktionsausdruck nicht sagen konnten und die keine Ahnung hatten, was ein sofort aufgerufener Funktionsausdruck ist.

Ich möchte jedoch eine sehr wichtige Sache erwähnen, nämlich, dass Premasagars Code-Snippet nicht funktionieren würde, selbst wenn er ihm eine Namenskennung gegeben hätte.

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

Der Grund, warum dies nicht funktionieren würde, ist, dass die JavaScript-Engine dies als Funktionsdeklaration interpretiert, gefolgt von einem völlig unabhängigen Gruppierungsoperator, der keinen Ausdruck enthält, und Gruppierungsoperatoren müssen einen Ausdruck enthalten. Laut JavaScript entspricht der obige Codeausschnitt dem folgenden.

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

();

Eine andere Sache, auf die ich hinweisen möchte, die für manche Menschen von Nutzen sein kann, ist, dass jede Namenskennung, die Sie für einen Funktionsausdruck angeben, im Kontext des Codes so gut wie nutzlos ist, außer innerhalb der Funktionsdefinition selbst.

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

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

Natürlich ist die Verwendung von Namenskennungen mit Ihren Funktionsdefinitionen immer hilfreich, wenn es um das Debuggen von Code geht, aber das ist etwas ganz anderes ... :-)


17

Gute Antworten wurden bereits veröffentlicht. Ich möchte jedoch darauf hinweisen, dass Funktionsdeklarationen einen leeren Abschlussdatensatz zurückgeben:

14.1.20 - Laufzeitsemantik: Auswertung

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Geben Sie NormalCompletion (leer) zurück.

Diese Tatsache ist nicht leicht zu beobachten, da die meisten Methoden zum Abrufen des zurückgegebenen Werts die Funktionsdeklaration in einen Funktionsausdruck konvertieren. Allerdings evalzeigt es:

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

Das Aufrufen eines leeren Abschlussdatensatzes macht keinen Sinn. Deshalb function f(){}()kann ich nicht arbeiten. Tatsächlich versucht die JS-Engine nicht einmal, sie aufzurufen. Die Klammern werden als Teil einer anderen Anweisung betrachtet.

Wenn Sie die Funktion jedoch in Klammern setzen, wird sie zu einem Funktionsausdruck:

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

Funktionsausdrücke geben ein Funktionsobjekt zurück. Und deshalb können Sie es nennen : (function f(){})().


Schade, dass diese Antwort übersehen wird. Obwohl es nicht so umfassend ist wie die akzeptierte Antwort, bietet es einige sehr nützliche zusätzliche Informationen und verdient mehr Stimmen
Avrohom Yisroel

10

In Javascript wird dies als sofort aufgerufener Funktionsausdruck (IIFE) bezeichnet .

Um es zu einem Funktionsausdruck zu machen, müssen Sie:

  1. füge es mit () bei

  2. Stellen Sie einen leeren Operator davor

  3. Weisen Sie es einer Variablen zu.

Andernfalls wird es als Funktionsdefinition behandelt, und Sie können es dann nicht gleichzeitig auf folgende Weise aufrufen / aufrufen:

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

Das obige gibt Ihnen Fehler. Weil Sie einen Funktionsausdruck nur sofort aufrufen können.

Dies kann auf verschiedene Arten erreicht werden: Weg 1:

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

Weg 2:

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

Weg 3:

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

Weg 4:

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

Alle oben genannten Elemente rufen sofort den Funktionsausdruck auf.


3

Ich habe nur eine weitere kleine Bemerkung. Ihr Code funktioniert mit einer kleinen Änderung:

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

Ich verwende die obige Syntax anstelle der weiter verbreiteten Version:

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

weil ich es nicht geschafft habe, dass der Einzug für Javascript-Dateien in vim richtig funktioniert. Es scheint, dass vim die geschweiften Klammern in offenen Klammern nicht mag.


Warum funktioniert diese Syntax, wenn Sie das ausgeführte Ergebnis einer Variablen zuweisen, aber nicht eigenständig?
Paislee

1
@paislee - Da die JavaScript-Engine jede gültige JavaScript-Anweisung, die mit dem functionSchlüsselwort beginnt, als Funktionsdeklaration() interpretiert. In diesem Fall wird das Trailing als Gruppierungsoperator interpretiert , der gemäß den JavaScript-Syntaxregeln nur einen JavaScript-Ausdruck enthalten kann und muss .
Natlee75

1
@bosonix - Ihre bevorzugte Syntax funktioniert gut, aber es ist eine gute Idee, aus Gründen der Konsistenz entweder die von Ihnen referenzierte "weiter verbreitete Version" oder die Variante zu verwenden, ()die im Gruppierungsoperator enthalten ist (die von Douglas Crockford dringend empfohlene): Es ist üblich, IIFEs zu verwenden, ohne sie einer Variablen zuzuweisen, und es ist leicht zu vergessen, diese umschließenden Klammern einzuschließen, wenn Sie sie nicht konsistent verwenden.
Natlee75

0

Vielleicht wäre die kürzere Antwort das

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

ist ein Funktionsliteral , das eine (anonyme) Funktion definiert. Ein zusätzliches () -Paar, das als Ausdruck interpretiert wird, wird auf oberster Ebene nicht erwartet, sondern nur Literale.

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

befindet sich in einer Ausdrucksanweisung , die eine anonyme Funktion aufruft .


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

Oben ist eine gültige Syntax angegeben, da alles, was in Klammern übergeben wird, als Funktionsausdruck betrachtet wird.

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

Oben ist keine gültige Syntax. Da der Java-Skriptsyntax-Parser nach dem Funktionsschlüsselwort nach dem Funktionsnamen sucht, da er nichts findet, wird ein Fehler ausgegeben.


2
Während Ihre Antwort nicht falsch ist, deckt die akzeptierte Antwort dies alles bereits in der Abteilung ab.
Bis Arnold

0

Sie können es auch wie folgt verwenden:

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

oder

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

!- negation op konvertiert die fn-Definition in einen fn-Ausdruck, daher können Sie sie sofort mit aufrufen (). Gleich wie mit 0,fn defodervoid fn def


-1

Sie können mit Parameter-Argumenten wie verwendet werden

var x = 3; 
var y = 4;

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

würde als 7 ergeben


-1

Diese zusätzliche Klammer erstellt zusätzliche anonyme Funktionen zwischen dem globalen Namespace und der anonymen Funktion, die den Code enthält. Und in Javascript-Funktionen, die in anderen Funktionen deklariert sind, kann nur auf den Namespace der übergeordneten Funktion zugegriffen werden, die sie enthält. Da zwischen dem globalen Bereich und dem tatsächlichen Code-Scoping ein zusätzliches Objekt (anonyme Funktion) besteht, wird es nicht beibehalten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.