Wyjaśnij enkapsulowaną składnię funkcji anonimowych


372

Podsumowanie

Czy potrafisz wyjaśnić, co kryje się za składnią enkapsulowanych funkcji anonimowych w JavaScript? Dlaczego to działa: (function(){})();ale to nie function(){}();:?


Co wiem

W JavaScript tworzy się nazwaną funkcję taką jak ta:

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

Możesz także utworzyć anonimową funkcję i przypisać ją do zmiennej:

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

Możesz obudować blok kodu, tworząc anonimową funkcję, a następnie zawijając ją w nawiasach i natychmiast wykonując:

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

Jest to przydatne podczas tworzenia skryptów modularnych, aby uniknąć zaśmiecania bieżącego zakresu lub zasięgu globalnego potencjalnie sprzecznymi zmiennymi - jak w przypadku skryptów Greasemonkey, wtyczek jQuery itp.

Teraz rozumiem, dlaczego to działa. Nawiasy zamykają zawartość i ujawniają tylko wynik (jestem pewien, że można to lepiej opisać), na przykład za pomocą (2 + 2) === 4.


Czego nie rozumiem

Ale nie rozumiem, dlaczego to nie działa równie dobrze:

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

Czy możesz mi to wyjaśnić?


39
Myślę, że wszystkie te zróżnicowane notacje i sposoby definiowania / ustawiania / wywoływania funkcji są najbardziej mylącą częścią początkowej pracy z javascript. Ludzie też nie mówią o nich. To nie jest podkreślony punkt w przewodnikach lub blogach. Powoduje to mdłości, ponieważ dla większości ludzi jest to mylące, a ludzie biegli w js również musieli przez to przejść. To jest jak ta pusta rzeczywistość tabu, o której nigdy się nie mówi.
ahnbizcad

1
Przeczytaj także o celu tego konstruktu lub sprawdź wyjaśnienie ( techniczne ) (także tutaj ). W celu umieszczenia nawiasu zapoznaj się z tym pytaniem o ich lokalizacji .
Bergi,

OT: Dla tych, którzy chcą wiedzieć, gdzie te anonimowe funkcje są często używane, proszę przeczytać odpowiedniogogood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi

Jest to typowy przypadek wyrażeń funkcji natychmiast wywołanych (IIFE).
Aminu Kano,

Odpowiedzi:


410

Nie działa, ponieważ jest analizowany jako a FunctionDeclaration, a identyfikator nazwy deklaracji funkcji jest obowiązkowy .

Gdy otaczasz go nawiasami, jest on oceniany jako a FunctionExpression, a wyrażenia funkcyjne można nazwać lub nie.

Gramatyka FunctionDeclarationwygląda następująco:

function Identifier ( FormalParameterListopt ) { FunctionBody }

I FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Jak widać token Identifier(Identyfikator opt ) FunctionExpressionjest opcjonalny, dlatego możemy mieć wyrażenie funkcyjne bez zdefiniowanej nazwy:

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

Lub nazwane wyrażenie funkcji:

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

Nawiasy (formalnie nazywane operatorem grupowania ) mogą otaczać tylko wyrażenia, a wyrażenie funkcji jest oceniane.

Dwie produkcje gramatyczne mogą być niejednoznaczne i mogą wyglądać dokładnie tak samo, na przykład:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Analizator składni wie, czy jest to a FunctionDeclarationlub a FunctionExpression, w zależności od kontekstu, w którym się pojawia.

W powyższym przykładzie drugi jest wyrażeniem, ponieważ operator przecinka może również obsługiwać tylko wyrażenia.

Z drugiej strony, FunctionDeclarationmogą faktycznie występować tylko w tak zwanym Programkodzie, co oznacza kod poza zakresem globalnym i wewnątrz FunctionBodyinnych funkcji.

Należy unikać funkcji wewnątrz bloków, ponieważ mogą one prowadzić do nieprzewidzianych zachowań, np .:

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

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

Powyższy kod powinien faktycznie wygenerować SyntaxError , ponieważ a Blockmoże zawierać tylko instrukcje (a Specyfikacja ECMAScript nie definiuje żadnej instrukcji funkcji), ale większość implementacji jest tolerancyjna i po prostu przyjmuje drugą funkcję, tę, która zaalarmuje 'false!'.

Implementacje Mozilli - Rhino, SpiderMonkey - mają inne zachowanie. Ich gramatyka zawiera niestandardowe instrukcję funkcji, co oznacza, że ​​funkcja będzie oceniana w czasie wykonywania , a nie w czasie analizy, jak to ma miejsce w przypadku FunctionDeclarations. W tych implementacjach zdefiniujemy pierwszą funkcję.


Funkcje można zadeklarować na różne sposoby, porównaj następujące :

1- Funkcja zdefiniowana za pomocą konstruktora funkcji przypisanego do zmiennej zwielokrotnia :

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

2- Deklaracja funkcji o nazwie mnożącej się :

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

3- Wyrażenie funkcyjne przypisane do zmiennej zwielokrotnia się :

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

4- Nazwane wyrażenie funkcyjne func_name , przypisane do zmiennej pomnóż :

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

2
Odpowiedź CMS jest poprawna. Aby uzyskać doskonałe dogłębne objaśnienie deklaracji funkcji i wyrażeń, zobacz ten artykuł autorstwa kangax .
Tim Down

To świetna odpowiedź. Wydaje się, że jest to ściśle związane z analizą tekstu źródłowego i strukturą BNF. w twoim przykładzie 3, czy powinienem powiedzieć, że jest to wyrażenie funkcyjne, ponieważ występuje po znaku równości, czy ta forma jest deklaracją / instrukcją funkcji, gdy pojawia się sama w wierszu? Zastanawiam się, jaki by to był cel - czy jest to po prostu interpretowane jako deklaracja funkcji o nazwie, ale bez nazwy? Jaki to ma cel, jeśli nie przypisujesz jej do zmiennej, nie nazywasz jej ani nie wywołujesz?
Breton

1
Aha. Bardzo przydatne. Dzięki CMS. Ta część dokumentów Mozilli, z którymi się łączysz, jest szczególnie pouczająca: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar

1
+1, chociaż nawias zamykający znajdował się w niewłaściwej pozycji w wyrażeniu funkcji :-)
NickFitz

1
@GovindRai, Nie. Deklaracje funkcji są obsługiwane w czasie kompilacji, a duplikat deklaracji funkcji zastępuje poprzednią deklarację. W czasie wykonywania deklaracja funkcji jest już dostępna, aw tym przypadku dostępna jest ta, która ostrzega „fałsz”. Aby uzyskać więcej informacji, przeczytaj , że nie znasz JS
Saurabh Misra

50

Chociaż jest to stare pytanie i odpowiedź, omawia temat, który do dziś rzuca wielu programistów na pętlę. Nie mogę policzyć liczby kandydatów na programistów JavaScript, z którymi przeprowadziłem wywiady, którzy nie mogliby powiedzieć mi różnicy między deklaracją funkcji a wyrażeniem funkcji i którzy nie mieli pojęcia, co to jest natychmiast wywołane wyrażenie funkcji.

Chciałbym jednak wspomnieć o jednej bardzo ważnej rzeczy, że fragment kodu Premasagar nie działałby, nawet gdyby nadał mu identyfikator nazwy.

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

Powodem, dla którego to nie działa, jest to, że silnik JavaScript interpretuje to jako deklarację funkcji, po której następuje całkowicie niepowiązany operator grupowania, który nie zawiera wyrażenia, a operatory grupowania muszą zawierać wyrażenie. Zgodnie z JavaScriptem powyższy fragment kodu jest równoważny z następującym.

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

();

Inną rzeczą, na którą chciałbym zwrócić uwagę, że może być użyteczny dla niektórych osób, jest to, że każdy identyfikator nazwy, który podajesz dla wyrażenia funkcji, jest prawie bezużyteczny w kontekście kodu, z wyjątkiem samej definicji funkcji.

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

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

Oczywiście używanie identyfikatorów nazw w definicjach funkcji jest zawsze pomocne, jeśli chodzi o kod debugowania, ale to coś zupełnie innego ... :-)


17

Świetne odpowiedzi zostały już opublikowane. Ale chcę zauważyć, że deklaracje funkcji zwracają pusty rekord zakończenia:

14.1.20 - Runtime Semantics: ewaluacja

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Zwraca wartość NormalCompletion (pusta).

Fakt ten nie jest łatwy do zaobserwowania, ponieważ większość sposobów uzyskania zwróconej wartości przekształci deklarację funkcji w wyrażenie funkcyjne. Jednak evalpokazuje, że:

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

Wywołanie pustego rekordu ukończenia nie ma sensu. Dlatego function f(){}()nie może działać. W rzeczywistości silnik JS nawet nie próbuje go wywołać, nawiasy są uważane za część innej instrukcji.

Ale jeśli owiniesz funkcję w nawiasy, stanie się ona wyrażeniem funkcji:

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

Wyrażenia funkcyjne zwracają obiekt funkcji. A zatem można nazwać to: (function f(){})().


Szkoda, że ​​ta odpowiedź została przeoczona. Chociaż nie jest tak wyczerpująca, jak zaakceptowana odpowiedź, zawiera kilka bardzo przydatnych dodatkowych informacji i zasługuje na więcej głosów
Avrohom Yisroel

10

W javascript nazywa się to Expressmed-Invoked Function Expression (IIFE) .

Aby było to wyrażenie funkcyjne, musisz:

  1. dołącz to za pomocą ()

  2. umieść przed nim operatora pustki

  3. przypisz ją do zmiennej.

W przeciwnym razie będzie traktowane jako definicja funkcji, a wtedy nie będzie można wywoływać / wywoływać go w tym samym czasie w następujący sposób:

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

Powyższe spowoduje błąd. Ponieważ możesz wywołać wyrażenie funkcji tylko natychmiast.

Można to osiągnąć na kilka sposobów: Sposób 1:

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

Sposób 2:

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

Sposób 3:

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

sposób 4:

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

Wszystkie powyższe natychmiast wywołają wyrażenie funkcji.


3

Mam jeszcze jedną małą uwagę. Twój kod będzie działał z niewielką zmianą:

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

Używam powyższej składni zamiast bardziej rozpowszechnionej wersji:

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

ponieważ nie udało mi się sprawić, aby wcięcie działało poprawnie dla plików javascript w vimie. Wygląda na to, że vim nie lubi nawiasów klamrowych w otwartym nawiasie.


Dlaczego więc ta składnia działa po przypisaniu wykonanego wyniku do zmiennej, ale nie jest samodzielna?
paislee

1
@paislee - Ponieważ silnik JavaScript interpretuje każdą prawidłową instrukcję JavaScript zaczynającą się od functionsłowa kluczowego jako deklarację funkcji, w którym to przypadku trailing ()jest interpretowany jako operator grupujący, który zgodnie z regułami składni JavaScript może i musi zawierać wyrażenie JavaScript.
natlee75,

1
@bosonix - Twoja preferowana składnia działa dobrze, ale dla zachowania spójności warto użyć albo „bardziej rozpowszechnionej wersji”, do której się odwołujesz, albo wariantu, w którym ()znajduje się operator operatora grupowania (ten, który zdecydowanie zaleca Douglas Crockford): powszechne jest używanie IIFE bez przypisywania ich do zmiennej, i łatwo zapomnieć o dołączeniu tych nawiasów, jeśli nie używasz ich konsekwentnie.
natlee75,

0

Być może krótsza odpowiedź byłaby taka

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

jest literałem funkcji, który definiuje (anonimową) funkcję. Dodatkowa para (), interpretowana jako wyrażenie, nie jest oczekiwana na najwyższym poziomie, tylko literały.

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

znajduje się w wyrażeniu, które wywołuje funkcję anonimową.


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

Powyżej jest poprawna składnia, ponieważ wszystko przekazane w nawiasie jest traktowane jako wyrażenie funkcji.

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

Powyżej jest niepoprawna składnia. Ponieważ parser składni skryptu Java szuka nazwy funkcji po słowie kluczowym funkcji, ponieważ nie znajduje niczego, zgłasza błąd.


2
Chociaż twoja odpowiedź nie jest niepoprawna, zaakceptowana odpowiedź obejmuje to wszystko w dept.
Do Arnold

0

Możesz także użyć go w następujący sposób:

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

lub

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

!- negation op konwertuje definicję fn na wyrażenie fn, dlatego można ją wywołać natychmiast za pomocą (). Taki sam jak przy użyciu 0,fn deflubvoid fn def


-1

Można ich używać z parametrami-argumentami takimi jak

var x = 3; 
var y = 4;

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

wyniósłoby 7


-1

Te dodatkowe nawiasy tworzą dodatkowe anonimowe funkcje między globalną przestrzenią nazw a anonimową funkcją zawierającą kod. A w funkcjach JavaScript zadeklarowanych wewnątrz innych funkcji można uzyskać dostęp tylko do przestrzeni nazw funkcji nadrzędnej, która je zawiera. Ponieważ istnieje dodatkowy obiekt (funkcja anonimowa) między zakresem globalnym a rzeczywistym zasięgiem kodu nie jest zachowywany.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.