Qual è lo scopo di racchiudere interi file Javascript in funzioni anonime come “(function () {…}) ()”?


584

Ultimamente ho letto molto Javascript e ho notato che l'intero file è racchiuso come segue nei file .js da importare.

(function() {
    ... 
    code
    ...
})();

Qual è la ragione per fare questo piuttosto che un semplice insieme di funzioni di costruzione?


6
Dal momento che immagino che questo verrà utilizzato da molte persone, per favore non dimenticare la chiusura;
dgh

5
Questa tecnica si chiama "IIFE" penso. Questo sta per Espressione di funzione immediatamente invocata en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Risposte:


786

Di solito è lo spazio dei nomi (vedi più avanti) e controlla la visibilità delle funzioni e / o delle variabili dei membri. Pensalo come una definizione di oggetto. Il nome tecnico per questo è un ' Espressione immediata della funzione (IIFE). I plugin jQuery sono generalmente scritti in questo modo.

In Javascript, puoi annidare le funzioni. Quindi, è legale quanto segue:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Ora puoi chiamare outerFunction(), ma la visibilità di innerFunction()è limitata allo scopo di outerFunction(), nel senso che è privata outerFunction(). Fondamentalmente segue lo stesso principio delle variabili in Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

corrispondentemente:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

Nello scenario sopra, puoi chiamare globalFunction()da qualsiasi luogo, ma non puoi chiamare localFunction1o localFunction2.

Quello che stai facendo quando scrivi (function() { ... })()è rendere letterale la funzione all'interno del primo set di parentesi (il che significa che l'intero "oggetto" è in realtà una funzione). Successivamente, stai auto-invocando la funzione (la finale ()) che hai appena definito. Quindi il principale vantaggio di questo, come ho detto prima, è che puoi avere metodi / funzioni e proprietà private:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

Nel primo esempio, invoceresti esplicitamente globalFunctionper nome per eseguirlo. Cioè, faresti semplicemente globalFunction()per eseguirlo. Ma nell'esempio sopra, non stai semplicemente definendo una funzione; lo stai definendo e invocando in una volta sola. Ciò significa che quando il tuo file JavaScript viene caricato, viene immediatamente eseguito. Certo, potresti fare:

function globalFunction() {
    // code
}
globalFunction();

Il comportamento sarebbe sostanzialmente lo stesso, tranne per una differenza significativa: si evita di inquinare l'ambito globale quando si utilizza un IIFE (di conseguenza significa anche che non è possibile invocare la funzione più volte poiché non ha un nome, ma poiché questa funzione è pensata per essere eseguita solo quando non è un problema).

La cosa bella con gli IIFE è che puoi anche definire le cose all'interno ed esporre solo le parti che vuoi al mondo esterno, quindi (un esempio di spazio dei nomi in modo da poter fondamentalmente creare la tua libreria / plugin):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Ora puoi chiamare myPlugin.public_function1(), ma non puoi accedere private_function()! Così simile alla definizione di una classe. Per capirlo meglio, raccomando i seguenti collegamenti per ulteriori letture:

MODIFICARE

Ho dimenticato di menzionare. In quella finale (), puoi passare tutto quello che vuoi dentro. Ad esempio, quando si creano plugin jQuery, si passa jQueryo in questo $modo:

(function(jQ) { ... code ... })(jQuery) 

Quindi, quello che stai facendo qui è definire una funzione che accetta un parametro (chiamato jQ, una variabile locale e noto solo a quella funzione). Quindi stai auto-invocando la funzione e passando un parametro (chiamato anche jQuery, ma questo viene dal mondo esterno e un riferimento al jQuery stesso). Non c'è bisogno urgente di farlo, ma ci sono alcuni vantaggi:

  • È possibile ridefinire un parametro globale e assegnargli un nome sensato nell'ambito locale.
  • Vi è un leggero vantaggio in termini di prestazioni poiché è più veloce cercare elementi nell'ambito locale invece di dover percorrere la catena dell'ambito nell'ambito globale.
  • Ci sono vantaggi per la compressione (minificazione).

In precedenza ho descritto come queste funzioni vengono eseguite automaticamente all'avvio, ma se vengono eseguite automaticamente chi passa gli argomenti? Questa tecnica presuppone che tutti i parametri necessari siano già definiti come variabili globali. Quindi se jQuery non fosse già definito come variabile globale questo esempio non funzionerebbe. Come puoi immaginare, una delle cose che jquery.js fa durante la sua inizializzazione è definire una variabile globale 'jQuery', così come la sua variabile globale '$' più famosa, che consente a questo codice di funzionare dopo che è stato incluso jQuery.


14
Molto bello, capisco bene lo spazio dei nomi, ma ho visto molti dei tuoi ultimi esempi e non sono riuscito a capire cosa la gente stesse cercando di ottenere. Questo chiarisce davvero le cose.
Andrew Kou,

34
Post fantastico. Molte grazie.
Darren,

4
Penso che aggiungere un punto e virgola iniziale e finale ';' renderebbe l'esempio completo - In ;(function(jQ) { ... code ... })(jQuery);questo modo se qualcuno lasciasse un punto e virgola nella sua sceneggiatura, ciò non spezzerebbe il tuo, specialmente se prevedi di minimizzare e concatenare la tua sceneggiatura con altri.
Taras Alenin,

3
bel post, mi piace l'enfasi sulle variabili private. Mi piace anche l'apertura sul modulo-modello / chiusure (public_function1 & public_function2) e come si passano le variabili, anche se leggermente fuori campo è una bella introduzione. Ho anche aggiunto una risposta, questa incentrata su ciò che suppongo siano le radici della sintassi e le differenze tra dichiarazione di funzione vs espressione di funzione e ciò che penso sia "solo una convenzione" vs "l'unico modo per ottenere questo risultato".
Adrien,

4
Ottimo post, penso forse di più su come il passaggio delle variabili nella funzione di auto-esecuzione sia vantaggioso. Il contesto nella funzione di auto-esecuzione è pulito - nessun dato. Puoi passare nel contesto facendo questo (function (context) { ..... })(this)che ti consente quindi di collegare tutto ciò che ti piace al contesto principale esponendolo in tal modo.
Callum Linington,

79

In breve

Sommario

Nella sua forma più semplice, questa tecnica mira a racchiudere il codice all'interno di un ambito di funzione .

Aiuta a ridurre le possibilità di:

  • scontro con altre applicazioni / librerie
  • inquinamento di portata superiore (probabilmente globale)

Essa non rileva quando il documento è pronto - non è una sorta didocument.onload nonwindow.onload

È comunemente noto come Immediately Invoked Function Expression (IIFE)oSelf Executing Anonymous Function .

Codice spiegato

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Nell'esempio sopra, qualsiasi variabile definita nella funzione (cioè dichiarata usando var) sarà "privata" e accessibile SOLO nell'ambito della funzione (come dice Vivin Paliath). In altre parole, queste variabili non sono visibili / raggiungibili al di fuori della funzione. Guarda la demo dal vivo .

Javascript ha l'ambito delle funzioni. "I parametri e le variabili definiti in una funzione non sono visibili al di fuori della funzione e che una variabile definita ovunque all'interno di una funzione è visibile ovunque all'interno della funzione." (da "Javascript: le parti buone").


Più dettagli

Codice alternativo

Alla fine, il codice precedentemente pubblicato poteva anche essere fatto come segue:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Guarda la demo dal vivo .


Le radici

Iterazione 1

Un giorno qualcuno probabilmente pensò "deve esserci un modo per evitare di nominare 'myMainFunction', poiché tutto ciò che vogliamo è eseguirlo immediatamente".

Se torni alle basi, scopri che:

  • expression: qualcosa che valuta un valore. vale a dire3+11/x
  • statement: riga (e) di codice che fa qualcosa, MA non valuta un valore. vale a direif(){}

Allo stesso modo, le espressioni di funzioni valutano un valore. E una conseguenza (presumo?) È che possono essere immediatamente invocati:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Quindi il nostro esempio più complesso diventa:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Guarda la demo dal vivo .

Iterazione 2

Il prossimo passo è il pensiero "perché var myMainFunction =se non lo usiamo nemmeno !?".

La risposta è semplice: prova a rimuoverlo, come di seguito:

 function(){ console.log('mamamia!'); }();

Guarda la demo dal vivo .

Non funzionerà perché "le dichiarazioni di funzione non sono invocabili" .

Il trucco è che rimuovendo var myMainFunction =abbiamo trasformato l' espressione della funzione in una dichiarazione di funzione . Vedi i link in "Risorse" per maggiori dettagli al riguardo.

La domanda successiva è "perché non posso mantenerlo come espressione di funzione con qualcosa di diverso da var myMainFunction =?

La risposta è "puoi", e in realtà ci sono molti modi in cui potresti farlo: aggiungendo a +, a !, a -, o magari racchiudendo un paio di parentesi (come è ora fatto per convenzione), e altro credo. Per esempio:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

o

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

o

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Quindi, una volta che la modifica pertinente viene aggiunta a quello che un tempo era il nostro "Codice alternativo", torniamo allo stesso identico codice di quello usato nell'esempio "Codice spiegato"

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Maggiori informazioni su Expressions vs Statements:


Scopi demistificanti

Una cosa che ci si potrebbe chiedere è "cosa succede quando NON si definisce la variabile" correttamente "all'interno della funzione, ovvero si fa un semplice compito?"

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Guarda la demo dal vivo .

Fondamentalmente, se a una variabile che non è stata dichiarata nel suo ambito attuale viene assegnato un valore, "una ricerca nella catena dell'ambito si verifica fino a quando non trova la variabile o raggiunge l'ambito globale (a quel punto la creerà)".

In un ambiente browser (rispetto a un ambiente server come nodejs) l'ambito globale è definito windowdall'oggetto. Quindi possiamo fare window.myOtherFunction().

Il mio consiglio "Buone pratiche" su questo argomento è di utilizzare sempre varquando si definisce qualcosa : che si tratti di un numero, oggetto o funzione e anche quando si è nell'ambito globale. Questo rende il codice molto più semplice.

Nota:

  • javascript non ha block scope(Aggiornamento: variabili locali dell'ambito del blocco aggiunte in ES6 .)
  • javascript ha solo function scope& global scope( windowambito in un ambiente browser)

Maggiori informazioni su Javascript Scopes:


risorse


Prossimi passi

Una volta ottenuto questo IIFEconcetto, questo porta a module pattern, che è comunemente fatto sfruttando questo schema IIFE. Divertiti :)


Molto utile. Molte grazie!
Christoffer Helgelin Hald,

Bello, preferisco la versione demo :)
Fabrizio Bertoglio

Tale grande spiegazione. Grazie!
Vikram Khemlani,

26

Javascript in un browser ha solo un paio di ambiti efficaci: ambito di funzioni e ambito globale.

Se una variabile non è nell'ambito della funzione, è nell'ambito globale. E le variabili globali sono generalmente cattive, quindi questo è un costrutto per mantenere le variabili di una libreria su se stesso.


1
Ma la stessa funzione di costruzione non fornisce lo spazio per le proprie variabili?
Andrew Kou,

1
Sì, ogni funzione definita in questa libreria potrebbe definire le proprie variabili locali, ma ciò consente alle variabili di essere condivise tra le funzioni senza che fuoriuscano dalla libreria
Gareth

@Gareth, quindi questo consente variabili "globali" in un ambito (;
Francisco Presencia,

2
@FranciscoPresencia "globale in un ambito" non è una frase utile, perché in pratica è proprio questo che significa "ambito". Il punto centrale dell'ambito "globale" è che è specificamente l'ambito a cui hanno accesso tutti gli altri ambiti.
Gareth,

19

Si chiama chiusura. In pratica sigilla il codice all'interno della funzione in modo che altre librerie non interferiscano con esso. È simile alla creazione di uno spazio dei nomi in linguaggi compilati.

Esempio. Supponiamo che io scriva:

(function() {

    var x = 2;

    // do stuff with x

})();

Ora altre librerie non possono accedere alla variabile che xho creato per utilizzare nella mia libreria.


7
Attento alla tua terminologia. La spaziatura dei nomi implica che è possibile accedere alle variabili dall'esterno indirizzando lo spazio dei nomi (in genere utilizzando un prefisso). Mentre questo è possibile in Javascript che non è ciò che è dimostrato qui
Gareth

Sono d'accordo che non è esattamente come uno spazio dei nomi, tuttavia, è in grado di fornire una funzionalità simile restituendo un oggetto con proprietà che si desidera pubblicizzare: (function(){ ... return { publicProp1: 'blah' }; })();. Ovviamente non perfettamente parallelo allo spazio dei nomi, ma può aiutare a pensarlo in quel modo.
Gioele

nel tuo esempio x è ancora una variabile privata ... Nonostante tu la avvolga in un IIFE. vai avanti e prova ad accedere a x al di fuori della funzione, non puoi ..
RayLoveless,

Il tuo punto non è valido. Anche nella seguente funzione altre librerie non possono accedere a x. function () {var x = 2}
RayLoveless

@RayLoveless Sono d'accordo. Non contraddico questa affermazione. In effetti, ho fatto la stessa affermazione dell'ultima frase di questa risposta.
Gioele

8

Puoi usare le chiusure delle funzioni come dati anche in espressioni più grandi, come in questo metodo per determinare il supporto del browser per alcuni degli oggetti html5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }

Cosa fa il !! fare?
1,21 gigawatt

!! converte un valore nella sua rappresentazione booleana (vero / falso).
Liam,

7

Oltre a mantenere le variabili locali, un uso molto utile è quando si scrive una libreria utilizzando una variabile globale, è possibile assegnarle un nome di variabile più breve da utilizzare all'interno della libreria. Viene spesso utilizzato nella scrittura di plugin jQuery, poiché jQuery consente di disabilitare la variabile $ che punta a jQuery, utilizzando jQuery.noConflict (). Nel caso in cui sia disabilitato, il tuo codice può comunque usare $ e non rompersi se lo fai semplicemente:

(function($) { ...code...})(jQuery);

3
  1. Per evitare lo scontro con altri metodi / librerie nella stessa finestra,
  2. Evita l'ambito globale, rendilo locale,
  3. Per velocizzare il debug (ambito locale),
  4. JavaScript ha solo l'ambito delle funzioni, quindi aiuterà anche nella compilazione di codici.

1

Dovremmo anche usare "use strict" nella funzione scope per assicurarci che il codice debba essere eseguito in "modalità rigorosa". Codice di esempio mostrato di seguito

(function() {
    'use strict';

    //Your code from here
})();

Perché dovremmo usare rigoroso?
nbro,


Non risponde davvero alla domanda!
Pritam Banerjee,

Pritam, è una buona pratica. Per favore, fai delle ricerche adeguate prima di votare qualsiasi risposta
Neha Jain,

1
'usa rigoroso' salva i programmatori cattivi da se stessi. E poiché la maggior parte dei programmatori sono cattivi programmatori, aiuta a impedire loro di fare cose che sicuramente non dovrebbero fare e finire in un disordine di codice che affonda rapidamente.
MattE
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.