function chiusura - Come funzionano le chiusure JavaScript?




closure in (25)

Questo è un tentativo di chiarire alcuni (possibili) equivoci sulle chiusure che appaiono in alcune delle altre risposte.

  • Una chiusura non viene creata solo quando si restituisce una funzione interna. Di fatto, la funzione di chiusura non ha bisogno di tornare affatto per poter creare la chiusura.Potresti invece assegnare la tua funzione interiore a una variabile in un ambito esterno o passarla come argomento a un'altra funzione in cui potrebbe essere richiamata immediatamente o in qualsiasi momento successivo. Pertanto, la chiusura della funzione di chiusura viene probabilmente creata non appena viene chiamata la funzione di chiusura poiché ogni funzione interna ha accesso a tale chiusura ogni volta che viene richiamata la funzione interna, prima o dopo il ritorno della funzione di chiusura.
  • Una chiusura non fa riferimento a una copia dei vecchi valori delle variabili nel suo ambito. Le variabili stesse fanno parte della chiusura, quindi il valore visualizzato quando si accede a una di queste variabili è l'ultimo valore al momento dell'accesso. Questo è il motivo per cui le funzioni interne create all'interno di loop possono essere complicate, dato che ognuna ha accesso alle stesse variabili esterne invece di afferrare una copia delle variabili nel momento in cui la funzione viene creata o chiamata.
  • Le "variabili" in una chiusura includono tutte le funzioni con nome dichiarate all'interno della funzione. Includono anche argomenti della funzione. Una chiusura ha anche accesso alle sue variabili di chiusura contenenti, fino allo scopo globale.
  • Le chiusure utilizzano la memoria, ma non causano perdite di memoria dal momento che JavaScript di per sé ripulisce le proprie strutture circolari che non sono referenziate. Le perdite di memoria di Internet Explorer che coinvolgono le chiusure vengono create quando non riesce a disconnettere i valori degli attributi del DOM che fanno riferimento alle chiusure, mantenendo quindi i riferimenti a possibili strutture circolari.

Come spiegheresti le chiusure di JavaScript a qualcuno con una conoscenza dei concetti in cui consistono (ad esempio funzioni, variabili e simili), ma non capisce le chiusure stesse?

Ho visto l'esempio Scheme dato su Wikipedia, ma sfortunatamente non è stato d'aiuto.


Tendo a imparare meglio con i confronti GOOD / BAD. Mi piace vedere il codice di lavoro seguito da un codice non funzionante che qualcuno potrebbe incontrare. Metto insieme un jsFiddle che fa un confronto e cerca di ridurre le differenze alle spiegazioni più semplici che potrei inventare .

Chiusure fatte bene:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • Nel codice precedente createClosure(n)viene richiamato in ogni iterazione del ciclo. Si noti che ho chiamato la variabile nper evidenziare che si tratta di una nuova variabile creata in un nuovo ambito di funzione e non è la stessa variabile indexassociata all'ambito esterno.

  • Questo crea un nuovo ambito ed nè legato a tale ambito; questo significa che abbiamo 10 ambiti separati, uno per ogni iterazione.

  • createClosure(n) restituisce una funzione che restituisce n all'interno di tale ambito.

  • All'interno di ogni ambito nè associato a qualsiasi valore avesse quando è createClosure(n)stato invocato in modo che la funzione nidificata che viene restituita restituirà sempre il valore di nquello che aveva quando è createClosure(n)stato richiamato.

Chiusure fatte male:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • Nel codice precedente il ciclo è stato spostato all'interno della createClosureArray()funzione e la funzione ora restituisce solo l'array completato, che a prima vista sembra più intuitivo.

  • Ciò che potrebbe non essere ovvio è che poiché createClosureArray()viene invocato solo una volta viene creato un solo scope per questa funzione invece di uno per ogni iterazione del ciclo.

  • All'interno di questa funzione indexviene definita una variabile denominata . Il ciclo viene eseguito e aggiunge funzioni alla matrice che restituisce index. Nota che indexè definita all'interno della createClosureArrayfunzione che viene sempre invocata una sola volta.

  • Poiché all'interno della createClosureArray()funzione era presente un solo ambito , indexè associato solo a un valore all'interno di tale ambito. In altre parole, ogni volta che il loop cambia il valore di index, lo cambia per tutto ciò che fa riferimento a tale ambito.

  • Tutte le funzioni aggiunte all'array restituiscono la indexvariabile SAME dall'ambito principale in cui è stata definita anziché 10 diverse da 10 diversi ambiti come il primo esempio. Il risultato finale è che tutte e 10 le funzioni restituiscono la stessa variabile dallo stesso ambito.

  • Una volta terminato il ciclo e dopo indexaverlo modificato, il valore finale era 10, quindi ogni funzione aggiunta all'array restituisce il valore della singola indexvariabile che ora è impostata su 10.

Risultato

CLOSURES DONE RIGHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


I bambini ricorderanno sempre i segreti che hanno condiviso con i loro genitori, anche dopo che i loro genitori se ne sono andati. Questo è ciò che le chiusure sono per le funzioni.

I segreti per le funzioni JavaScript sono le variabili private

var parent = function() {
 var name = "Mary"; // secret
}

Ogni volta che lo chiami, viene creata la variabile locale "nome" e viene dato il nome "Maria". E ogni volta che la funzione esce, la variabile viene persa e il nome viene dimenticato.

Come puoi immaginare, poiché le variabili vengono ricreate ogni volta che viene chiamata la funzione, e nessun altro le conoscerà, deve esserci un luogo segreto in cui vengono memorizzate. Potrebbe essere chiamato Chamber of Secrets o stack o scope locale ma non ha molta importanza. Sappiamo che sono lì, da qualche parte, nascosti nella memoria.

Ma in JavaScript c'è questa cosa molto speciale che le funzioni che sono create all'interno di altre funzioni, possono anche conoscere le variabili locali dei loro genitori e tenerle per tutto il tempo in cui vivono.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Quindi, finché siamo nella funzione genitore, può creare una o più funzioni figlio che condividono le variabili segrete dal luogo segreto.

Ma la cosa triste è che se il bambino è anche una variabile privata della sua funzione genitore, morirebbe anche quando il genitore finirà, e i segreti morirebbero con loro.

Quindi, per vivere, il bambino deve partire prima che sia troppo tardi

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

E ora, anche se Mary "non sta più correndo", il ricordo di lei non è perso e il suo bambino ricorderà sempre il suo nome e altri segreti che hanno condiviso durante il loro tempo insieme.

Quindi, se chiami il bambino "Alice", lei risponderà

child("Alice") => "My name is Alice, child of Mary"

Questo è tutto quello che c'è da dire.


Una chiusura è dove una funzione interna ha accesso alle variabili nella sua funzione esterna. Questa è probabilmente la più semplice spiegazione a una linea che puoi ottenere per chiusure.


L'autore di Closures ha spiegato le chiusure abbastanza bene, spiegando il motivo per cui abbiamo bisogno di loro e spiegando anche LexicalEnvironment, che è necessario per capire chiusure.
Ecco il riassunto:

Cosa succede se si accede a una variabile, ma non è locale? Come qui:

In questo caso, l'interprete trova la variabile LexicalEnvironmentnell'oggetto esterno .

Il processo si compone di due passaggi:

  1. Innanzitutto, quando viene creata una funzione f, non viene creata in uno spazio vuoto. C'è un oggetto LexicalEnvironment corrente. Nel caso precedente, è finestra (a non è definita al momento della creazione della funzione).

Quando viene creata una funzione, ottiene una proprietà nascosta, denominata [[Ambito]], che fa riferimento all'attuale ambiente Lexical.

Se una variabile viene letta, ma non può essere trovata da nessuna parte, viene generato un errore.

Funzioni annidate

Le funzioni possono essere annidate l'una dentro l'altra, formando una catena di ambienti Lexical che può anche essere definita una catena di portata.

Quindi, la funzione g ha accesso a g, ae f.

chiusure

Una funzione nidificata può continuare a vivere dopo che la funzione esterna ha terminato:

Marcatura di ambienti Lexical:

Come si vede, this.sayè una proprietà nell'oggetto utente, quindi continua a vivere dopo aver completato l'utente.

E se ricordi, quando this.sayviene creato, esso (come ogni funzione) ottiene un riferimento interno this.say.[[Scope]]all'attuale ambiente Lexical. Quindi, il LexicalEnvironment dell'esecuzione corrente dell'utente rimane in memoria. Tutte le variabili dell'utente sono anche le sue proprietà, quindi sono anche tenute con cura, non annullate come di consueto.

Il punto è assicurarsi che se la funzione interiore vuole accedere a una variabile esterna in futuro, è in grado di farlo.

Riassumere:

  1. La funzione interna mantiene un riferimento al LexicalEnvironment esterno.
  2. La funzione interna può accedere a variabili da esso in qualsiasi momento anche se la funzione esterna è finita.
  3. Il browser mantiene in memoria il LexicalEnvironment e tutte le sue proprietà (variabili) finché non vi è una funzione interna che la fa riferimento.

Questo è chiamato una chiusura.


Wikipedia sulle chiusure :

Nell'informatica, una chiusura è una funzione insieme a un ambiente di riferimento per i nomi non locali (variabili libere) di quella funzione.

Tecnicamente, in JavaScript , ogni funzione è una chiusura . Ha sempre accesso a variabili definite nell'ambito circostante.

Poiché la costruzione di definizione di scope in JavaScript è una funzione , non un blocco di codice come in molti altri linguaggi, ciò che di solito intendiamo per chiusura in JavaScript è una funzione che funziona con variabili non locali definite nella funzione circostante già eseguita .

Le chiusure sono spesso utilizzate per creare funzioni con alcuni dati privati ​​nascosti (ma non è sempre il caso).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

L'esempio precedente utilizza una funzione anonima, che è stata eseguita una volta. Ma non deve essere. Può essere chiamato (es. mkdb) Ed eseguito successivamente, generando una funzione di database ogni volta che viene richiamato. Ogni funzione generata avrà il proprio oggetto di database nascosto. Un altro esempio di utilizzo delle chiusure è quando non restituiamo una funzione, ma un oggetto che contiene più funzioni per scopi diversi, ognuna delle quali ha accesso agli stessi dati.


Stai dormendo e inviti Dan. Dì a Dan di portare un controller XBox.

Dan invita Paul. Dan chiede a Paul di portare un controller. Quanti controllori sono stati portati alla festa?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

Una funzione in JavaScript non è solo un riferimento a un insieme di istruzioni (come in linguaggio C), ma include anche una struttura di dati nascosta che è composta da riferimenti a tutte le variabili non locali che usa (variabili catturate). Tali funzioni a due pezzi sono chiamate chiusure. Ogni funzione in JavaScript può essere considerata una chiusura.

Le chiusure sono funzioni con uno stato. È in qualche modo simile a "questo" nel senso che "questo" fornisce anche lo stato per una funzione ma la funzione e "questo" sono oggetti separati ("questo" è solo un parametro di fantasia, e l'unico modo per legarlo in modo permanente ad un la funzione è creare una chiusura). Mentre "questa" e la funzione vivono sempre separatamente, una funzione non può essere separata dalla sua chiusura e il linguaggio non fornisce alcun mezzo per accedere alle variabili catturate.

Poiché tutte queste variabili esterne a cui fa riferimento una funzione annidata lessicalmente sono in realtà variabili locali nella catena delle sue funzioni di inclusione lessicale (le variabili globali possono essere assunte come variabili locali di alcune funzioni di root) e ogni singola esecuzione di una funzione crea nuove istanze di le sue variabili locali, ne consegue che ogni esecuzione di una funzione che restituisce (o altrimenti lo trasferisce, come registrarla come callback) una funzione nidificata crea una nuova chiusura (con un proprio insieme potenzialmente unico di variabili non locali referenziate che rappresentano la sua esecuzione contesto).

Inoltre, si deve comprendere che le variabili locali in JavaScript non vengono create nello stack frame, ma nello heap e distrutte solo quando nessuno le sta facendo riferimento. Quando una funzione ritorna, i riferimenti alle sue variabili locali vengono decrementati, ma possono ancora essere non nulli se durante l'esecuzione corrente diventano parte di una chiusura e sono ancora referenziati dalle sue funzioni lessical nidificate (il che può accadere solo se i riferimenti a queste funzioni annidate sono state restituite o altrimenti trasferite su qualche codice esterno).

Un esempio:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

Come padre di un bambino di 6 anni, attualmente insegnando ai bambini piccoli (e un parente inesperto alla codifica senza istruzione formale, saranno necessarie correzioni), penso che la lezione si attenga meglio attraverso il gioco pratico. Se il bambino di 6 anni è pronto a capire che cos'è una chiusura, allora è abbastanza grande per andare da solo. Suggerirei di incollare il codice in jsfiddle.net, spiegandone un po 'e lasciandoli da soli a inventare una canzone unica. Il testo esplicativo di seguito è probabilmente più appropriato per un bambino di 10 anni.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

ISTRUZIONI

DATI: i dati sono una raccolta di fatti. Può essere numeri, parole, misure, osservazioni o anche solo descrizioni di cose. Non puoi toccarlo, annusarlo o assaggiarlo. Puoi scriverlo, parlarlo e ascoltarlo. Potresti usarlo per creare l' odore e il sapore del tocco usando un computer. Può essere reso utile da un computer usando il codice.

CODICE: tutta la scrittura sopra è chiamata codice . È scritto in JavaScript.

JAVASCRIPT: JavaScript è una lingua. Come l'inglese o il francese o il cinese sono le lingue. Esistono molte lingue comprese dai computer e da altri processori elettronici. Perché JavaScript sia compreso da un computer ha bisogno di un interprete. Immagina se un insegnante che parla solo russo venga ad insegnare la tua classe a scuola. Quando l'insegnante dice "все садятся", la classe non capirebbe. Ma per fortuna hai un alunno russo nella tua classe che dice a tutti che questo significa "tutti si siedono" - così tutti voi. La classe è come un computer e l'allievo russo è l'interprete. Per JavaScript l'interprete più comune è chiamato browser.

BROWSER: quando ti connetti a Internet su un computer, tablet o telefono per visitare un sito web, utilizzi un browser. Esempi che potresti conoscere sono Internet Explorer, Chrome, Firefox e Safari. Il browser può capire JavaScript e dire al computer cosa deve fare. Le istruzioni JavaScript sono chiamate funzioni.

FUNZIONE: una funzione in JavaScript è come una fabbrica. Potrebbe essere una piccola fabbrica con una sola macchina al suo interno. Oppure potrebbe contenere molte altre piccole fabbriche, ognuna con molte macchine che svolgono diversi lavori. In una vera e propria fabbrica di vestiti per la vita potresti avere delle risme di stoffa e di fili di filo infilati e t-shirt e jeans che escono. La nostra fabbrica JavaScript elabora solo i dati, non può cucire, praticare un foro o fondere il metallo. Nei nostri dati di fabbrica JavaScript entra e i dati vengono fuori.

Tutta questa roba di dati sembra un po 'noiosa, ma è davvero molto interessante; potremmo avere una funzione che dice a un robot cosa preparare per cena. Diciamo che invito te e il tuo amico a casa mia. Ti piacciono le cosce di pollo, mi piacciono le salsicce, il tuo amico vuole sempre quello che vuoi e il mio amico non mangia carne.

Non ho tempo per fare shopping, quindi la funzione deve sapere cosa abbiamo in frigo per prendere decisioni. Ogni ingrediente ha un tempo di cottura diverso e vogliamo che tutto sia servito caldo dal robot allo stesso tempo. Abbiamo bisogno di fornire la funzione con i dati su ciò che ci piace, la funzione potrebbe 'parlare' al frigorifero e la funzione potrebbe controllare il robot.

Normalmente una funzione ha un nome, parentesi e parentesi graffe. Come questo:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Si noti che /*...*/e il //codice di arresto viene letto dal browser.

NOME: puoi chiamare una funzione qualunque sia la parola che vuoi. L'esempio "cookMeal" è tipico nell'unire due parole insieme e dare alla seconda una lettera maiuscola all'inizio, ma questo non è necessario. Non può contenere uno spazio e non può essere un numero a sé stante.

PARENTHESES: "Parentesi" o ()casella postale sulla porta della factory di funzioni JavaScript o casella postale in strada per l'invio di pacchetti di informazioni alla fabbrica. A volte la casella postale potrebbe essere contrassegnata per esempio cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , nel qual caso si sa quali dati si devono dare.

BRETELLE: "Bretelle" che assomigliano a questo {}sono le finestre colorate della nostra fabbrica. Dall'interno della fabbrica puoi vedere, ma dall'esterno non puoi vedere.

L'ESEMPIO DI CODICE LUNGO SOPRA

Il nostro codice inizia con la funzione parola , quindi sappiamo che è uno! Quindi il nome della funzione sing - questa è la mia descrizione di cosa tratta la funzione. Quindi parentesi () . Le parentesi sono sempre lì per una funzione. A volte sono vuoti, e, talvolta, hanno qualcosa in questo si ha una parola in.: (person). Dopo questo c'è un tutore come questo {. Questo segna l'inizio della funzione sing () . Ha un partner che segna la fine di cantare () come questo}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Quindi questa funzione potrebbe avere qualcosa a che fare con il canto e potrebbe aver bisogno di alcuni dati su una persona. Ha delle istruzioni dentro per fare qualcosa con quei dati.

Ora, dopo la funzione sing () , vicino alla fine del codice c'è la linea

var person="an old lady";

VARIABILE: le lettere var indicano "variabile". Una variabile è come una busta. All'esterno questa busta è contrassegnata come "persona". All'interno contiene un foglietto di carta con le informazioni di cui la nostra funzione ha bisogno, alcune lettere e spazi uniti come un pezzo di corda (si chiama una stringa) che fa una frase che legge "una vecchia signora". La nostra busta potrebbe contenere altri tipi di cose come numeri (chiamati interi), istruzioni (chiamate funzioni), elenchi (chiamati array ). Poiché questa variabile è scritta al di fuori di tutte le parentesi graffe {}e poiché è possibile vedere attraverso le finestre colorate quando ci si trova all'interno delle parentesi graffe, questa variabile può essere vista da qualsiasi parte del codice. La chiamiamo una "variabile globale".

VARIABILE GLOBALE: la persona è una variabile globale, il che significa che se cambi il suo valore da "una vecchia signora" a "un giovane uomo", la persona continuerà ad essere un giovane finché non deciderai di cambiarla di nuovo e qualsiasi altra funzione in il codice può vedere che è un giovane uomo. Premi il F12pulsante o guarda le impostazioni di Opzioni per aprire la console di sviluppo di un browser e digitare "persona" per vedere qual è questo valore. Digita person="a young man"per cambiarlo e quindi digita "person" di nuovo per vedere che è cambiato.

Dopo questo abbiamo la linea

sing(person);

Questa linea chiama la funzione, come se stesse chiamando un cane

"Vieni a cantare , vieni a prendere la persona !"

Quando il browser ha caricato il codice JavaScript e ha raggiunto questa linea, avvierà la funzione. Metto la linea alla fine per assicurarmi che il browser abbia tutte le informazioni necessarie per eseguirlo.

Le funzioni definiscono le azioni - la funzione principale riguarda il canto. Contiene una variabile chiamata firstPart che si applica al canto della persona che si applica a ciascuno dei versi della canzone: "C'era" + persona + "che inghiottì". Se si digita firstPart nella console, non si otterrà una risposta perché la variabile è bloccata in una funzione - il browser non può vedere all'interno delle finestre colorate delle parentesi graffe.

CHIUSURE: le chiusure sono le funzioni più piccole che si trovano all'interno della funzione big sing () . Le piccole fabbriche all'interno della grande fabbrica. Ognuno di loro ha le proprie parentesi, il che significa che le variabili al loro interno non possono essere viste dall'esterno. Ecco perché i nomi delle variabili ( creatura e risultato ) possono essere ripetuti nelle chiusure ma con valori diversi. Se digiti questi nomi di variabili nella finestra della console, non otterrai il suo valore perché è nascosto da due livelli di finestre colorate.

Le chiusure sanno tutte quale sia la variabile della funzione sing () chiamata firstPart , perché possono vedere dalle loro finestre colorate.

Dopo le chiusure arrivano le linee

fly();
spider();
bird();
cat();

La funzione sing () chiamerà ognuna di queste funzioni nell'ordine in cui vengono fornite. Quindi il lavoro della funzione sing () verrà eseguito.


Le chiusure sono semplici:

Il seguente esempio semplice copre tutti i punti principali delle chiusure di JavaScript. *

Ecco una fabbrica che produce calcolatrici che possono aggiungere e moltiplicare:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Il punto chiave: ogni chiamata per make_calculatorcreare una nuova variabile locale n, che continua ad essere utilizzabile da quella calcolatrice adde multiplyfunziona molto dopo i make_calculatorritorni.

Se hai familiarità con gli stack frame, questi calcolatori sembrano strani: come possono continuare ad accedere ndopo i make_calculatorresi? La risposta è immaginare che JavaScript non usi "stack frames", ma usi invece "heap frames", che possono persistere dopo la chiamata alla funzione che li ha fatti restituire.

Le funzioni interne come adde multiply, che accedono alle variabili dichiarate in una funzione esterna ** , sono chiamate chiusure .

Questo è praticamente tutto ciò che c'è da chiudere.


* Ad esempio, copre tutti i punti nell'articolo "Chiusure for Dummies" fornito in un'altra risposta , eccetto l'esempio 6, che mostra semplicemente che le variabili possono essere utilizzate prima di essere dichiarate, un fatto piacevole da conoscere ma completamente estraneo alle chiusure. Copre anche tutti i punti nella risposta accettata , ad eccezione dei punti (1) che le funzioni copiano i loro argomenti in variabili locali (gli argomenti della funzione con nome), e (2) che i numeri di copia creano un nuovo numero, ma copiando un riferimento oggetto ti dà un altro riferimento allo stesso oggetto. Questi sono anche buoni da sapere ma ancora completamente estranei alle chiusure. È anche molto simile all'esempio di questa risposta, ma un po 'più breve e meno astratto. Non copre il punto diquesta risposta o questo commento , ovvero che JavaScript rende difficile collegare la correntevalore di una variabile di ciclo nella tua funzione interna: il passaggio "plug-in" può essere fatto solo con una funzione di aiuto che racchiude la tua funzione interna e viene invocata su ogni iterazione del ciclo. (In senso stretto, la funzione interna accede alla copia della variabile della funzione di aiuto, piuttosto che avere qualcosa collegato.) Ancora una volta, molto utile quando si creano chiusure, ma non fa parte di cosa sia una chiusura o come funzioni. Vi è ulteriore confusione dovuta alle chiusure che funzionano in modo diverso nei linguaggi funzionali come ML, dove le variabili sono legate ai valori piuttosto che allo spazio di archiviazione, fornendo un flusso costante di persone che comprendono le chiusure in un modo (cioè il modo "plug-in") che è semplicemente errato per JavaScript, dove le variabili sono sempre legate allo spazio di archiviazione e mai ai valori.

** Qualsiasi funzione esterna, se più nodi sono nidificati o anche nel contesto globale, come indica chiaramente questa risposta .


So che ci sono già molte soluzioni, ma immagino che questo script piccolo e semplice possa essere utile per dimostrare il concetto:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

OK, fan delle chiusure di 6 anni. Vuoi sentire l'esempio più semplice di chiusura?

Immaginiamo la prossima situazione: un autista è seduto in una macchina. Quella macchina è dentro un aereo. L'aereo è in aeroporto. La capacità del guidatore di accedere a cose al di fuori della sua auto, ma all'interno dell'aereo, anche se quell'aereo lascia un aeroporto, è una chiusura. Questo è tutto.Quando compi il 27, guarda la spiegazione più dettagliata o nell'esempio qui sotto.

Ecco come posso convertire la mia storia piana nel codice.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");


Ho creato un tutorial interattivo su JavaScript per spiegare come funzionano le chiusure. Cos'è una chiusura?

Ecco uno degli esempi:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

Come lo spiegherei a un bambino di sei anni:

Sai come gli adulti possono possedere una casa e la chiamano casa? Quando una mamma ha un figlio, il bambino non possiede proprio nulla, giusto? Ma i suoi genitori possiedono una casa, quindi ogni volta che qualcuno chiede al bambino "Dov'è la tua casa?", Lui / lei può rispondere "a quella casa!", E indica la casa dei suoi genitori. Una "chiusura" è la capacità del bambino di sempre (anche se all'estero) essere in grado di dire che ha una casa, anche se è proprio il genitore a possedere la casa.


Prendendo seriamente la domanda, dovremmo scoprire che un tipico bambino di 6 anni è capace di cognitivamente, anche se, ammettiamolo, chi è interessato a JavaScript non è così tipico.

Sullo sviluppo dell'infanzia: da 5 a 7 anni dice:

Il tuo bambino sarà in grado di seguire le indicazioni in due passaggi. Ad esempio, se dici a tuo figlio "Vai in cucina e prendi un sacchetto della spazzatura", saranno in grado di ricordare quella direzione.

Possiamo usare questo esempio per spiegare chiusure, come segue:

La cucina è una chiusura che ha una variabile locale, chiamata trashBags . C'è una funzione all'interno della cucina chiamata getTrashBag che ottiene un cestino e lo restituisce.

Possiamo codificare questo in JavaScript come questo:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Ulteriori punti che spiegano perché le chiusure sono interessanti:

  • Ogni volta che makeKitchen() viene chiamato, viene creata una nuova chiusura con il proprio trashBags separato.
  • La variabile trashBags è locale all'interno di ogni cucina e non è accessibile all'esterno, ma la funzione interna sulla proprietà getTrashBag ha accesso ad essa.
  • Ogni chiamata di funzione crea una chiusura, ma non sarebbe necessario mantenere la chiusura intorno a meno che una funzione interna, che ha accesso all'interno della chiusura, possa essere chiamata dall'esterno della chiusura. Restituire l'oggetto con la funzione getTrashBag fa qui.

The Straw Man

Ho bisogno di sapere quante volte è stato cliccato un pulsante e fare qualcosa su ogni terzo clic ...

Soluzione abbastanza ovvia

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Ora funzionerà, ma invaliderà lo scope esterno aggiungendo una variabile, il cui unico scopo è quello di tenere traccia del conteggio. In alcune situazioni, sarebbe preferibile in quanto l'applicazione esterna potrebbe aver bisogno di accedere a queste informazioni. Ma in questo caso, stiamo cambiando solo il comportamento di ogni terzo clic, quindi è preferibile racchiudere questa funzionalità all'interno del gestore di eventi .

Considera questa opzione

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Notare alcune cose qui.

Nell'esempio sopra, sto usando il comportamento di chiusura di JavaScript. Questo comportamento consente a qualsiasi funzione di accedere all'ambito in cui è stato creato, a tempo indeterminato. Per applicare praticamente questo, invoco immediatamente una funzione che restituisce un'altra funzione, e poiché la funzione che sto restituendo ha accesso alla variabile di conteggio interna (a causa del comportamento di chiusura spiegato sopra), ne risulta un ambito privato per l'utilizzo da parte del risultante funzione ... Non è così semplice? Diluiamolo ...

Una semplice chiusura a una linea

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Tutte le variabili al di fuori della funzione restituita sono disponibili per la funzione restituita, ma non sono direttamente disponibili per l'oggetto funzione restituito ...

func();  // Alerts "val"
func.a;  // Undefined

Prendilo? Quindi, nel nostro esempio principale, la variabile count è contenuta all'interno della chiusura e sempre disponibile per il gestore eventi, quindi mantiene il suo stato da click a click.

Inoltre, questo stato di variabile privata è completamente accessibile, sia per le letture che per l'assegnazione alle sue variabili scope private.

Ecco qua; ora stai incapsulando completamente questo comportamento.

Post completo sul blog (comprese le considerazioni su jQuery)


Ok, parlando con un bambino di 6 anni, potrei usare le seguenti associazioni.

Immagina: stai giocando con i tuoi fratellini e sorelline in tutta la casa, e ti muovi con i tuoi giocattoli e ne porti alcuni nella stanza di tuo fratello maggiore. Dopo un po 'tuo fratello tornò dalla scuola e andò nella sua stanza, e lui vi rinchiuse, così ora non potevi accedere ai giocattoli lasciati lì in modo diretto. Ma potresti bussare alla porta e chiedere a tuo fratello quei giocattoli. Questo si chiama chiusura del giocattolo ; tuo fratello ha inventato per te, e ora è nel campo di applicazione esterno .

Confrontati con una situazione in cui una porta è stata bloccata da una brutta copia e nessuno all'interno (esecuzione di una funzione generale), poi un incendio locale si è verificato e ha bruciato la stanza (garbage collector: D), e poi una nuova stanza è stata costruita e ora puoi andartene un altro giocattolo (nuova istanza di funzione), ma mai gli stessi giocattoli che sono stati lasciati nella prima istanza della stanza.

Per un bambino avanzato vorrei mettere qualcosa come il seguente. Non è perfetto, ma ti fa sentire quello che è:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Come puoi vedere, i giocattoli lasciati nella stanza sono ancora accessibili tramite il fratello e non importa se la stanza è chiusa. Ecco un jsbin per giocarci.


Li ho semplicemente indirizzati alla pagina di Mozilla Closures . È la spiegazione migliore, più concisa e semplice delle basi di chiusura e dell'uso pratico che ho trovato. È altamente raccomandato a chiunque stia imparando JavaScript.

E sì, lo consiglierei anche a un bambino di 6 anni - se il bambino di 6 anni sta imparando le chiusure, allora è logico che siano pronti a comprendere la spiegazione concisa e semplice fornita nell'articolo.


Le chiusure sono difficili da spiegare perché sono usate per fare del lavoro comportamentale che tutti si aspettano intuitivamente di lavorare comunque. Trovo che il modo migliore per spiegarle (e il modo in cui ho imparato quello che fanno) è immaginare la situazione senza di loro:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Cosa succederebbe qui se JavaScript non conoscesse le chiusure? Basta sostituire la chiamata nell'ultima riga con il suo corpo del metodo (che è in pratica ciò che le chiamate di funzione fanno) e ottieni:

console.log(x + 3);

Ora, dov'è la definizione di x ? Non l'abbiamo definito nell'ambito attuale. L'unica soluzione è lasciare che plus5 porti il suo ambito (o meglio lo scope del suo genitore). In questo modo, x è ben definito ed è associato al valore 5.


Le funzioni JavaScript possono accedere a:

  1. argomenti
  2. Locali (cioè le loro variabili locali e le funzioni locali)
  3. Ambiente, che include:
    • Globali, incluso il DOM
    • qualsiasi cosa nelle funzioni esterne

Se una funzione accede al suo ambiente, la funzione è una chiusura.

Si noti che le funzioni esterne non sono necessarie, anche se offrono vantaggi di cui non discuto qui. Accedendo ai dati nel proprio ambiente, una chiusura mantiene vivi i dati. Nella sottocasi delle funzioni esterne / interne, una funzione esterna può creare dati locali ed eventualmente uscire, e tuttavia, se una o più funzioni interne sopravvivono dopo che la funzione esterna è terminata, la funzione interna (s) mantiene i dati locali della funzione esterna vivo.

Esempio di chiusura che utilizza l'ambiente globale:

Immagina che gli eventi Button Overflow Vote-Up e Vote-Down siano implementati come chiusure, voteUp_click e voteDown_click, che hanno accesso a variabili esterne isVotedUp e isVotedDown, che sono definite globalmente. (Per ragioni di semplicità, mi riferisco ai pulsanti di votazione delle domande di , non alla serie di pulsanti Rispondi voto.)

Quando l'utente fa clic sul pulsante VoteUp, la funzione voteUp_click verifica se isVotedDown == true per determinare se votare o semplicemente annullare un voto basso. La funzione voteUp_click è una chiusura perché sta accedendo al suo ambiente.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Tutte e quattro queste funzioni sono chiusure in quanto tutte accedono al loro ambiente.


Ogni volta che vedi la parola chiave function all'interno di un'altra funzione, la funzione inner ha accesso alle variabili nella funzione esterna.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Questo logarà sempre 16, perché la bar può accedere alla x che è stata definita come un argomento a foo , e può anche accedere a tmp da foo .

Questa è una chiusura. Una funzione non deve tornare per essere chiamata una chiusura. Semplicemente l'accesso a variabili esterne all'ambito lessicale immediato crea una chiusura .

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

La funzione precedente registrerà anche 16, perché la bar può ancora fare riferimento a x e tmp , anche se non è più direttamente all'interno dell'ambito.

Tuttavia, poiché tmp è ancora in agguato all'interno della chiusura della bar , viene anche incrementato. Sarà incrementato ogni volta che chiami bar .

L'esempio più semplice di chiusura è questo:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Quando viene invocata una funzione JavaScript, viene creato un nuovo contesto di esecuzione. Insieme agli argomenti della funzione e all'oggetto padre, questo contesto di esecuzione riceve anche tutte le variabili dichiarate al di fuori di esso (nell'esempio precedente, sia 'a' che 'b').

È possibile creare più di una funzione di chiusura, restituendone una lista o impostandole su variabili globali. Tutti questi si riferiscono alla stessa x e allo stesso tmp , non fanno le proprie copie.

Qui il numero x è un numero letterale. Come con altri letterali in JavaScript, quando viene chiamato foo , il numero x viene copiato in foo come argomento x .

D'altra parte, JavaScript usa sempre riferimenti quando si tratta di oggetti. Se dici, hai chiamato foo con un oggetto, la chiusura restituita farà riferimento a quell'oggetto originale!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Come previsto, ogni chiamata a bar(10) incrementerà x.memb . Ciò che potrebbe non essere previsto, è che x si riferisce semplicemente allo stesso oggetto della variabile di age ! Dopo un paio di chiamate al bar , age.memb sarà 2! Questo riferimento è la base per perdite di memoria con oggetti HTML.


Puoi spiegare chiusure a un bambino di 5 anni? *

Continuo a pensare che la spiegazione di Google funzioni molto bene ed è concisa:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

* Domanda AC #


Forse un po 'al di là di tutto tranne il più precoce dei bambini di sei anni, ma alcuni esempi che hanno contribuito a rendere il concetto di chiusura in JavaScript per me.

Una chiusura è una funzione che ha accesso all'ambito di un'altra funzione (le sue variabili e funzioni). Il modo più semplice per creare una chiusura è con una funzione all'interno di una funzione; il motivo è che in JavaScript una funzione ha sempre accesso all'ambito della sua funzione di contenimento.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

AVVISO: scimmia

Nell'esempio precedente, viene chiamata outerFunction che a sua volta chiama innerFunction. Nota come outerVar è disponibile per innerFunction, evidenziato dal suo corretto avviso del valore di outerVar.

Considerare ora quanto segue:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

AVVISO: scimmia

referenceToInnerFunction è impostato su outerFunction (), che restituisce semplicemente un riferimento a innerFunction. Quando viene chiamata referenceToInnerFunction, restituisce outerVar. Di nuovo, come sopra, questo dimostra che innerFunction ha accesso a outerVar, una variabile di ExternalFunction. Inoltre, è interessante notare che mantiene questo accesso anche dopo che ExternalFunction ha terminato l'esecuzione.

E qui è dove le cose diventano davvero interessanti. Se dovessimo sbarazzarci di outerFunction, diciamo impostalo su null, potresti pensare che referenceToInnerFunction perderà il suo accesso al valore di outerVar. Ma questo non è il caso.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

AVVISO: scimmia ALERT: scimmia

Ma come è così? In che modo referenceToInnerFunction può ancora conoscere il valore di outerVar ora che externalFunction è stato impostato su null?

La ragione per cui referenceToInnerFunction può ancora accedere al valore di outerVar è perché quando la chiusura è stata creata ponendo innerFunction all'interno di outerFunction, innerFunction ha aggiunto un riferimento all'ambito di externalFunction (le sue variabili e funzioni) alla sua catena di scope. Ciò significa che innerFunction ha un puntatore o riferimento a tutte le variabili di ExternalFunction, incluso outerVar. Quindi, anche quando outerFunction ha terminato l'esecuzione, o anche se è cancellato o impostato su null, le variabili nel suo ambito, come outerVar, rimangono in memoria a causa del riferimento eccezionale a loro sulla parte della innerFunction che è stata restituita a referenceToInnerFunction. Per liberare veramente outerVar e il resto delle variabili di External function dalla memoria, dovresti sbarazzarti di questo eccezionale riferimento a loro,dite impostando referenceToInnerFunction su null.

//////////

Altre due cose sulle chiusure da notare. Innanzitutto, la chiusura avrà sempre accesso agli ultimi valori della sua funzione di contenimento.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

AVVISO: gorilla

In secondo luogo, quando viene creata una chiusura, mantiene un riferimento a tutte le sue variabili e funzioni della funzione di chiusura; non arriva a scegliere. E tuttavia, le chiusure dovrebbero essere usate con parsimonia, o almeno con attenzione, dato che possono essere intensivi della memoria; molte variabili possono essere conservate in memoria molto tempo dopo che una funzione di contenimento ha terminato l'esecuzione.


Una risposta per un bambino di sei anni (ammesso che sappia cos'è una funzione e che cos'è una variabile e quali sono i dati):

Le funzioni possono restituire dati. Un tipo di dati che è possibile restituire da una funzione è un'altra funzione. Quando viene restituita la nuova funzione, tutte le variabili e gli argomenti utilizzati nella funzione che lo ha creato non scompaiono. Invece, quella funzione genitore "si chiude". In altre parole, nulla può guardare al suo interno e vedere le variabili utilizzate tranne la funzione restituita. Quella nuova funzione ha una capacità speciale di guardare indietro all'interno della funzione che lo ha creato e vedere i dati al suo interno.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Un altro modo molto semplice per spiegarlo è in termini di ambito:

Ogni volta che crei un ambito più piccolo all'interno di un ambito più ampio, l'ambito più piccolo sarà sempre in grado di vedere ciò che è nello scope più grande.


La differenza è nello scope delle variabili dichiarate con ciascuno.

In pratica, ci sono una serie di conseguenze utili della differenza di scopo:

  1. letle variabili sono visibili solo nel blocco di chiusura più vicino ( { ... }).
  2. letle variabili sono utilizzabili solo nelle righe di codice che si verificano dopo la dichiarazione della variabile (anche se sono state issate !).
  3. letle variabili non possono essere ridichiarate da un successivo varo let.
  4. Le letvariabili globali non vengono aggiunte windowall'oggetto globale .
  5. letle variabili sono facili da usare con chiusure (non causano condizioni di gara ).

Le restrizioni imposte letriducono la visibilità delle variabili e aumentano la probabilità che le collisioni di nome inaspettate vengano individuate in anticipo. Ciò rende più facile tracciare e ragionare sulle variabili, inclusa la loro reachability (aiutando a recuperare memoria inutilizzata).

Di conseguenza, le letvariabili hanno meno probabilità di causare problemi quando vengono utilizzate in programmi di grandi dimensioni o quando le strutture sviluppate in modo indipendente sono combinate in modi nuovi e inaspettati.

varpuò ancora essere utile se si è sicuri di volere l'effetto vincolante singolo quando si utilizza una chiusura in un ciclo (n. 5) o per dichiarare variabili globali esternamente visibili nel codice (n. 4). L'uso di varper le esportazioni può essere soppiantato se exportmigrano dallo spazio del transpiler e nel linguaggio principale.

Esempi

1. Non utilizzare al di fuori del blocco contenitore più vicino: questo blocco di codice genera un errore di riferimento perché il secondo utilizzo si xverifica al di fuori del blocco in cui è dichiarato con let:

{
    let x = 1;
}
console.log(`x is ${x}`);  // ReferenceError during parsing: "x is not defined".

Al contrario, lo stesso esempio con varopere.

2. Non usare prima della dichiarazione:
questo blocco di codice lancia un ReferenceErrorcodice prima che il codice possa essere eseguito perché xviene utilizzato prima che venga dichiarato:

{
    x = x + 1;  // ReferenceError during parsing: "x is not defined".
    let x;
    console.log(`x is ${x}`);  // Never runs.
}

Al contrario, lo stesso esempio con varparses e run senza generare eccezioni.

3. Nessuna ridichiarazione: il seguente codice dimostra che una variabile dichiarata con letpotrebbe non essere dichiarata in un secondo momento:

let x = 1;
let x = 2;  // SyntaxError: Identifier 'x' has already been declared

4. Globale non associato a window:

var button = "I cause accidents because my name is too common.";
let link = "Though my name is common, I am harder to access from other JS files.";
console.log(link);  // OK
console.log(window.link);  // undefined (GOOD!)
console.log(window.button);  // OK

5. Facile utilizzo con chiusure: le variabili dichiarate con varnon funzionano bene con chiusure all'interno di anelli. Ecco un semplice ciclo che emette la sequenza di valori che la variabile iha in diversi momenti nel tempo:

for (let i = 0; i < 5; i++) {
    console.log(`i is ${i}`), 125/*ms*/);
}

In particolare, questo produce:

i is 0
i is 1
i is 2
i is 3
i is 4

In JavaScript spesso utilizziamo le variabili in un momento successivo rispetto a quando sono state create. Quando lo dimostriamo ritardando l'output con una chiusura passata a setTimeout:

for (let i = 0; i < 5; i++) {
    setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}

... l'output rimane invariato fino a quando restiamo fedeli let. Al contrario, se avessimo usato var iinvece:

for (var i = 0; i < 5; i++) {
    setTimeout(_ => console.log(`i is ${i}`), 125/*ms*/);
}

... il loop emette inaspettatamente "i is 5" cinque volte:

i is 5
i is 5
i is 5
i is 5
i is 5




javascript function variables scope closures