Spiegare la sintassi della funzione anonima incapsulata


373

Sommario

Puoi spiegare il ragionamento alla base della sintassi per le funzioni anonime incapsulate in JavaScript? Perché funziona: (function(){})();ma questo non funziona function(){}();:?


Quello che so

In JavaScript, si crea una funzione denominata come questa:

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

Puoi anche creare una funzione anonima e assegnarla a una variabile:

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

È possibile incapsulare un blocco di codice creando una funzione anonima, quindi racchiudendolo tra parentesi ed eseguendolo immediatamente:

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

Ciò è utile quando si creano script modularizzati, per evitare di ingombrare l'ambito corrente o globale, con variabili potenzialmente in conflitto, come nel caso degli script Greasemonkey, dei plugin jQuery, ecc.

Ora capisco perché funziona. Le parentesi racchiudono il contenuto ed espongono solo il risultato (sono sicuro che c'è un modo migliore per descriverlo), come con (2 + 2) === 4.


Quello che non capisco

Ma non capisco perché questo non funzioni allo stesso modo:

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

Puoi spiegarmelo?


39
Penso che tutta questa varia notazione e modi di definire / impostare / chiamare le funzioni sia la parte più confusa di lavorare inizialmente con javascript. Le persone tendono a non parlarne neanche. Non è un punto enfatizzato nelle guide o nei blog. Mi viene in mente perché è qualcosa di confuso per la maggior parte delle persone, e anche le persone fluenti in js devono averlo attraversato. È come questa vuota realtà tabù di cui non si parla mai.
ahnbizcad,

1
Leggi anche lo scopo di questo costrutto o controlla una spiegazione ( tecnica ) (anche qui ). Per il posizionamento della parentesi, vedere questa domanda sulla loro posizione .
Bergi,

OT: Per coloro che vogliono sapere dove sono usate molte funzioni anonime, leggi adeguatamente.good.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi,

Questo è un tipico caso di Espressioni di funzione immediatamente invocate (IIFE).
Aminu Kano,

Risposte:


410

Non funziona perché viene analizzato come a FunctionDeclaratione l'identificatore del nome delle dichiarazioni di funzione è obbligatorio .

Quando lo circondi tra parentesi, viene valutato come un FunctionExpressione le espressioni di funzione possono essere nominate o meno.

La grammatica di un FunctionDeclarationassomiglia a questo:

function Identifier ( FormalParameterListopt ) { FunctionBody }

E FunctionExpression s:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Come puoi vedere l' opzioneIdentifier (Identificatore opt token ) in FunctionExpressionè facoltativo, quindi possiamo avere un'espressione di funzione senza un nome definito:

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

O espressione di funzione denominata :

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

Le parentesi (formalmente denominate Operatore di raggruppamento ) possono racchiudere solo espressioni e viene valutata un'espressione di funzione.

Le due produzioni grammaticali possono essere ambigue e possono apparire esattamente uguali, ad esempio:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Il parser sa se è a FunctionDeclarationo a FunctionExpression, a seconda del contesto in cui appare.

Nell'esempio sopra, il secondo è un'espressione perché l' operatore Comma può anche gestire solo espressioni.

D'altra parte, FunctionDeclarations potrebbe effettivamente apparire solo nel cosiddetto Programcodice " ", che significa codice all'esterno nell'ambito globale e all'interno FunctionBodydelle altre funzioni.

Le funzioni all'interno dei blocchi dovrebbero essere evitate, perché possono condurre un comportamento imprevedibile, ad esempio:

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

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

Il codice sopra dovrebbe effettivamente produrre un SyntaxError, poiché un Blockpuò contenere solo istruzioni (e la specifica ECMAScript non definisce alcuna istruzione di funzione), ma la maggior parte delle implementazioni sono tolleranti e prenderanno semplicemente la seconda funzione, quella che avvisa'false!' .

Le implementazioni di Mozilla - Rhino, SpiderMonkey - hanno un comportamento diverso. La loro grammatica contiene un'istruzione di funzione non standard , il che significa che la funzione verrà valutata in fase di esecuzione , non al momento dell'analisi, come accade con FunctionDeclarations. In queste implementazioni otterremo la prima funzione definita.


Le funzioni possono essere dichiarate in diversi modi, confrontare quanto segue :

1- Una funzione definita con il costruttore Funzione assegnata alla variabile si moltiplica :

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

2- Una dichiarazione di funzione di una funzione chiamata moltiplica :

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

3- Un'espressione di funzione assegnata alla variabile si moltiplica :

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

4- Un'espressione di funzione nominata nome_funzione , assegnata alla variabile moltiplicare :

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

2
La risposta di CMS è corretta. Per un'eccellente spiegazione approfondita delle dichiarazioni e delle espressioni di funzioni, consultare questo articolo di Kangax .
Tim Down,

Questa è un'ottima risposta Sembra essere intimamente collegato al modo in cui viene analizzato il testo sorgente e alla struttura del BNF. nel tuo esempio 3, dovrei dire che è un'espressione di funzione perché segue un segno di uguale, dove quel modulo è una dichiarazione / istruzione di funzione quando appare su una riga da solo? Mi chiedo quale sarebbe lo scopo - è solo interpretato come una dichiarazione di funzione denominata, ma senza un nome? A quale scopo serve se non lo stai assegnando a una variabile, non lo assegni o lo chiami?
Bretone,

1
Aha. Molto utile. Grazie, CMS. Questa parte dei documenti Mozilla a cui ti sei collegato è particolarmente illuminante: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
Premasagar,

1
+1, anche se la parentesi di chiusura era nella posizione errata nell'espressione della funzione :-)
NickFitz,

1
@GovindRai, No. Le dichiarazioni di funzioni vengono gestite in fase di compilazione e una dichiarazione di funzione duplicata sovrascrive la dichiarazione precedente. In fase di esecuzione, la dichiarazione di funzione è già disponibile e in questo caso, quella disponibile è quella che avvisa "falso". Per maggiori informazioni, leggi che non conosci JS
Saurabh Misra

50

Anche se questa è una vecchia domanda e risposta, discute un argomento che fino ad oggi lancia molti sviluppatori per un ciclo. Non riesco a contare il numero di candidati sviluppatori JavaScript che ho intervistato che non sono in grado di dirmi la differenza tra una dichiarazione di funzione e un'espressione di funzione e che non hanno idea di cosa sia un'espressione di funzione immediatamente invocata.

Vorrei menzionare, tuttavia, una cosa molto importante che è che lo snippet di codice di Premasagar non funzionerebbe anche se gli avesse assegnato un identificatore di nome.

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

Il motivo per cui non funzionerebbe è che il motore JavaScript lo interpreta come una dichiarazione di funzione seguita da un operatore di raggruppamento completamente non correlato che non contiene espressioni e che gli operatori di raggruppamento devono contenere un'espressione. Secondo JavaScript, lo snippet di codice sopra è equivalente al seguente.

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

();

Un'altra cosa che vorrei sottolineare che potrebbe essere di qualche utilità per alcune persone è che qualsiasi identificatore di nome fornito per un'espressione di funzione è praticamente inutile nel contesto del codice, tranne che all'interno della definizione della funzione stessa.

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

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

Ovviamente, l'uso degli identificatori di nome con le definizioni delle funzioni è sempre utile quando si tratta di debug del codice, ma è qualcos'altro del tutto ... :-)


17

Grandi risposte sono già state pubblicate. Ma voglio notare che le dichiarazioni di funzione restituiscono un record di completamento vuoto:

14.1.20 - Semantica di runtime: valutazione

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Restituisce NormalCompletion (vuoto).

Questo fatto non è facile da osservare, poiché la maggior parte dei modi per tentare di ottenere il valore restituito convertirà la dichiarazione di funzione in un'espressione di funzione. Tuttavia, lo evalmostra:

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

Chiamare un record di completamento vuoto non ha senso. Ecco perché function f(){}()non può funzionare. In effetti il ​​motore JS non tenta nemmeno di chiamarlo, le parentesi sono considerate parte di un'altra istruzione.

Ma se avvolgi la funzione tra parentesi, diventa un'espressione di funzione:

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

Le espressioni di funzione restituiscono un oggetto funzione. E quindi si può chiamare: (function f(){})().


Peccato che questa risposta sia trascurata. Sebbene non esaustiva come la risposta accettata, fornisce alcune informazioni extra molto utili e merita più voti
Avrohom Yisroel,

10

In javascript, questo si chiama IIFE (Immediately-Invoked Function Expression) .

Per renderlo un'espressione di funzione devi:

  1. racchiuderlo usando ()

  2. posizionare un operatore vuoto prima di esso

  3. assegnarlo a una variabile.

Altrimenti verrà trattata come definizione di funzione e quindi non sarà possibile chiamarla / invocarla contemporaneamente nel modo seguente:

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

Quanto sopra ti darà l'errore. Perché è possibile richiamare solo un'espressione di funzione immediatamente.

Questo può essere ottenuto in due modi: Modo 1:

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

Modo 2:

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

Modo 3:

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

modo 4:

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

Tutto quanto sopra invocherà immediatamente l'espressione della funzione.


3

Ho solo un'altra piccola osservazione. Il codice funzionerà con una piccola modifica:

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

Uso la sintassi sopra invece della versione più diffusa:

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

perché non sono riuscito a far funzionare correttamente il rientro per i file javascript in vim. Sembra che a Vim non piacciano le parentesi graffe all'interno della parentesi aperta.


Quindi perché questa sintassi funziona quando si assegna il risultato eseguito a una variabile, ma non autonomo?
Paislee,

1
@paislee - Poiché il motore JavaScript interpreta qualsiasi istruzione JavaScript valida che inizia con la functionparola chiave come una dichiarazione di funzione, nel qual caso il trailing ()viene interpretato come un operatore di raggruppamento che, secondo le regole della sintassi JavaScript, può solo e deve contenere un'espressione JavaScript.
natlee75,

1
@bosonix - La tua sintassi preferita funziona bene, ma è una buona idea utilizzare la "versione più diffusa" a cui hai fatto riferimento o la variante in cui ()è racchiuso l'operatore di raggruppamento (quello che Douglas Crockford raccomanda vivamente) per coerenza: è comune usare gli IIFE senza assegnarli a una variabile, ed è facile dimenticare di includere quelle parentesi avvolgenti se non le si usano in modo coerente.
natlee75,

0

Forse la risposta più breve sarebbe quella

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

è una funzione letterale che definisce una funzione (anonima). Un ulteriore () -pair, che viene interpretato come espressione, non è previsto al livello superiore, solo letterali.

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

è in una dichiarazione di espressione che richiama una funzione anonima.


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

Sopra è sintassi valida perché tutto ciò che è passato tra parentesi è considerato come espressione di funzione.

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

Sopra non è una sintassi valida. Poiché il parser di sintassi dello script java cerca il nome della funzione dopo la parola chiave della funzione poiché non trova nulla, genera un errore.


2
Sebbene la tua risposta non sia errata, la risposta accettata copre già tutto questo in profondità.
Fino a Arnold il

0

Puoi anche usarlo come:

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

o

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

!- negation op converte la definizione di fn in espressione di fn, pertanto è possibile richiamarla immediatamente con (). Come usare 0,fn defovoid fn def


-1

Possono essere usati con parametri-argomenti come

var x = 3; 
var y = 4;

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

risulterebbe come 7


-1

Quelle parentesi extra creano funzioni anonime extra tra lo spazio dei nomi globale e la funzione anonima che contiene il codice. E nelle funzioni Javascript dichiarate all'interno di altre funzioni è possibile accedere solo allo spazio dei nomi della funzione padre che le contiene. Poiché non viene mantenuto l'oggetto aggiuntivo (funzione anonima) tra l'ambito globale e l'ambito del codice effettivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.