function chiusura closure - Come funzionano le chiusure JavaScript?




15 Answers

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.

in () definizione

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.




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.



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.




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 scritto un post sul blog un po 'indietro spiegando chiusure. Ecco cosa ho detto sulle chiusure in termini di perché ne vorrai una.

Le chiusure sono un modo per consentire a una funzione di avere variabili permanenti, private , ovvero variabili di cui solo una funzione è a conoscenza, in cui può tenere traccia delle informazioni delle precedenti volte in cui è stata eseguita.

In questo senso, lasciano che una funzione agisca un po 'come un oggetto con attributi privati.

Post completo:

Quindi quali sono questi thingys di chiusura?




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.




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




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



Non capisco perché le risposte siano così complesse qui.

Ecco una chiusura:

var a = 42;

function b() { return a; }

Sì. Probabilmente lo usi più volte al giorno.


Non c'è motivo di credere che le chiusure siano un trucco complesso per risolvere problemi specifici. No, le chiusure servono solo a utilizzare una variabile che proviene da un ambito più elevato dalla prospettiva di dove è stata dichiarata la funzione (non eseguita) .

Ora quello che ti permette di fare può essere più spettacolare, vedere altre risposte.




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.




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.");



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.




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.




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();



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.






Related