Domanda Come funzionano le chiusure JavaScript?


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 ha aiutato.


7654


origine


risposte:


Chiusure di JavaScript per i principianti

Presentato da Morris su Tue, 21.02.2006 10:19. Pubblicato dalla community.

Le chiusure non sono magiche

Questa pagina spiega le chiusure in modo che un programmatore possa capirle - usando il codice JavaScript funzionante. Non è per guru o programmatori funzionali.

Le chiusure sono non difficile  capire una volta che il concetto di base è burrascoso. Tuttavia, sono impossibili da capire leggendo qualsiasi documento accademico o informazioni accademicamente orientate su di loro!

Questo articolo è destinato ai programmatori con una certa esperienza di programmazione in un linguaggio tradizionale e che possono leggere la seguente funzione JavaScript:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Un esempio di chiusura

Due sommari di una frase:

  • Una chiusura è un modo di sostenere funzioni di prima classe ; è un'espressione che può fare riferimento a variabili all'interno del suo ambito (quando è stato dichiarato per la prima volta), essere assegnato a una variabile, essere passato come argomento a una funzione o essere restituito come risultato di una funzione.

  • Oppure, una chiusura è uno stack frame che viene assegnato quando una funzione inizia la sua esecuzione, e non liberato  dopo che la funzione ritorna (come se un 'stack frame' fosse allocato sull'heap piuttosto che sullo stack!).

Il seguente codice restituisce un riferimento a una funzione:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

La maggior parte dei programmatori JavaScript comprenderà come un riferimento a una funzione viene restituito a una variabile ( say2) nel codice sopra. Se non lo fai, allora devi guardarlo prima di poter imparare chiusure. Un programmatore che usa C potrebbe pensare alla funzione come restituire un puntatore a una funzione e alle variabili say e say2 erano ciascuno un puntatore a una funzione.

Esiste una differenza fondamentale tra un puntatore C a una funzione e un riferimento JavaScript a una funzione. In JavaScript, puoi pensare a una variabile di riferimento di funzione come se avesse sia un puntatore a una funzione anche  come un puntatore nascosto a una chiusura.

Il codice sopra riportato ha una chiusura perché la funzione anonima function() { console.log(text); } è dichiarato dentro  un'altra funzione, sayHello2() in questo esempio. In JavaScript, se usi il function parola chiave all'interno di un'altra funzione, stai creando una chiusura.

In C e molte altre lingue comuni, dopo  una funzione ritorna, tutte le variabili locali non sono più accessibili perché lo stack-frame è distrutto.

In JavaScript, se dichiari una funzione all'interno di un'altra funzione, le variabili locali possono rimanere accessibili dopo il ritorno dalla funzione chiamata. Questo è dimostrato sopra, perché chiamiamo la funzione say2()dopo che siamo tornati da sayHello2(). Si noti che il codice che chiamiamo fa riferimento alla variabile text, che era un variabile locale  della funzione sayHello2().

function() { console.log(text); } // Output of say2.toString();

Guardando l'output di say2.toString(), possiamo vedere che il codice si riferisce alla variabile text. La funzione anonima può fare riferimento text che detiene il valore 'Hello Bob' perché le variabili locali di sayHello2() sono tenuti in una chiusura.

La magia è che in JavaScript un riferimento alla funzione ha anche un riferimento segreto alla chiusura in cui è stato creato - simile a come i delegati sono un puntatore del metodo più un riferimento segreto a un oggetto.

Altri esempi

Per qualche ragione, le chiusure sembrano davvero difficili da capire quando leggi su di esse, ma quando vedi alcuni esempi diventa chiaro come funzionano (ci ho messo un po 'di tempo). Raccomando di esaminare attentamente gli esempi finché non capisci come funzionano. Se inizi a utilizzare le chiusure senza comprendere appieno il loro funzionamento, presto creerai alcuni bug molto strani!

Esempio 3

Questo esempio mostra che le variabili locali non vengono copiate - sono mantenute per riferimento. È un po 'come mantenere in memoria uno stack frame quando esce la funzione esterna!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Esempio 4

Tutte e tre le funzioni globali hanno un riferimento comune a stesso  chiusura perché sono tutti dichiarati all'interno di una singola chiamata a setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Le tre funzioni hanno accesso condiviso alla stessa chiusura: le variabili locali di setupSomeGlobals() quando sono state definite le tre funzioni.

Nota che nell'esempio sopra, se chiami setupSomeGlobals() di nuovo, quindi viene creata una nuova chiusura (stack-frame!). La vecchia gLogNumber, gIncreaseNumber, gSetNumber le variabili vengono sovrascritte con nuovo  funzioni che hanno la nuova chiusura. (In JavaScript, ogni volta che si dichiara una funzione all'interno di un'altra funzione, le funzioni interne vengono / vengono ricreate di nuovo ogni  tempo in cui viene chiamata la funzione esterna.)

Esempio 5

Questo esempio mostra che la chiusura contiene tutte le variabili locali che sono state dichiarate all'interno della funzione esterna prima di uscire. Si noti che la variabile alice viene effettivamente dichiarato dopo la funzione anonima. La funzione anonima è dichiarata per prima; e quando viene chiamata quella funzione, può accedere a alice variabile perché alice è nello stesso ambito (JavaScript lo fa sollevamento variabile ). Anche sayAlice()() chiama direttamente il riferimento alla funzione restituita da sayAlice() - È esattamente uguale a quello che è stato fatto in precedenza, ma senza la variabile temporanea.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: nota anche che il say la variabile è anche all'interno della chiusura e potrebbe essere accessibile da qualsiasi altra funzione che potrebbe essere dichiarata all'interno sayAlice(), o potrebbe essere accesso in modo ricorsivo all'interno della funzione interna.

Esempio 6

Questo è un vero trucchetto per molte persone, quindi devi capirlo. Fai molta attenzione se stai definendo una funzione all'interno di un ciclo: le variabili locali della chiusura potrebbero non agire come potresti pensare prima.

Per comprendere questo esempio, devi comprendere la funzione di "sollevamento variabile" in Javascript.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

La linea result.push( function() {console.log(item + ' ' + list[i])} aggiunge un riferimento a una funzione anonima tre volte alla matrice dei risultati. Se non hai familiarità con le funzioni anonime, pensa come:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Si noti che quando si esegue l'esempio, "item2 undefined" è registrato tre volte! Questo perché, proprio come gli esempi precedenti, c'è solo una chiusura per le variabili locali per buildList (quali sono result, i e item). Quando le funzioni anonime vengono chiamate in linea fnlist[j](); usano tutti la stessa chiusura singola e usano il valore corrente per i e item all'interno di quella unica chiusura (dove i ha un valore di 3 perché il ciclo era completato, e item ha un valore di 'item2'). Nota che stiamo indicizzando da 0 quindi item ha un valore di item2. E l'i ++ aumenterà i al valore 3.

Potrebbe essere utile vedere cosa succede quando una dichiarazione a livello di blocco della variabile item è usato (tramite il let parola chiave) invece di una dichiarazione di variabili con scope funzione tramite il var parola chiave. Se viene apportata questa modifica, quindi ogni funzione anonima nell'array result ha una sua chiusura; quando viene eseguito l'esempio, l'output è il seguente:

item0 undefined
item1 undefined
item2 undefined

Se la variabile i viene anche definito utilizzando let invece di var, quindi l'output è:

item0 1
item1 2
item2 3

Esempio 7

In questo ultimo esempio, ogni chiamata alla funzione principale crea una chiusura separata.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Sommario

Se tutto sembra completamente non chiaro, la cosa migliore da fare è giocare con gli esempi. Leggere una spiegazione è molto più difficile che comprendere esempi. Le mie spiegazioni su chiusure e riquadri, ecc. Non sono tecnicamente corrette: sono semplificazioni grossolane intese a facilitare la comprensione. Una volta terminata l'idea di base, puoi raccogliere i dettagli più tardi.

Punti finali:

  • Ogni volta che usi function all'interno di un'altra funzione, viene utilizzata una chiusura.
  • Ogni volta che usi eval() all'interno di una funzione, viene utilizzata una chiusura. Il testo tu eval può fare riferimento a variabili locali della funzione e all'interno eval puoi anche creare nuove variabili locali usando eval('var foo = …')
  • Quando usi new Function(…) (il Costruttore di funzioni ) all'interno di una funzione, non crea una chiusura. (La nuova funzione non può fare riferimento alle variabili locali della funzione esterna.)
  • Una chiusura in JavaScript è come mantenere una copia di tutte le variabili locali, proprio come erano quando una funzione veniva chiusa.
  • Probabilmente è meglio pensare che una chiusura venga sempre creata solo come una voce di una funzione, e le variabili locali vengono aggiunte a quella chiusura.
  • Un nuovo insieme di variabili locali viene mantenuto ogni volta che viene chiamata una funzione con una chiusura (dato che la funzione contiene una dichiarazione di funzione al suo interno, e un riferimento a quella funzione interna viene restituito o viene mantenuto un riferimento esterno per esso in qualche modo ).
  • Due funzioni potrebbero sembrare che abbiano lo stesso testo sorgente, ma hanno un comportamento completamente diverso a causa della loro chiusura "nascosta". Non penso che il codice JavaScript possa effettivamente scoprire se un riferimento di funzione ha una chiusura o meno.
  • Se stai provando a fare delle modifiche al codice sorgente dinamico (per esempio: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), non funzionerà se myFunction è una chiusura (ovviamente, non si potrebbe nemmeno pensare di eseguire la sostituzione della stringa del codice sorgente in fase di esecuzione, ma ...).
  • È possibile ottenere dichiarazioni di funzione all'interno delle dichiarazioni di funzione all'interno delle funzioni e si possono ottenere chiusure a più di un livello.
  • Penso che normalmente una chiusura sia un termine sia per la funzione che per le variabili che vengono catturate. Nota che non uso quella definizione in questo articolo!
  • Sospetto che le chiusure in JavaScript differiscano da quelle normalmente presenti nei linguaggi funzionali.

link

Grazie

Se hai appena  chiusure apprese (qui o altrove!), quindi sono interessato a qualsiasi vostro commento su eventuali modifiche che potreste suggerire che potrebbero rendere più chiaro questo articolo. Invia una mail a morrisjohns.com (morris_closure @). Si prega di notare che io non sono un guru su JavaScript, né sulle chiusure.


Il post originale di Morris può essere trovato nel Internet Archive .


6159



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é bar può accedere a x che è stato definito come argomento a fooe può anche accedere tmp a partire dal foo.

Quella è  una chiusura. Una funzione non deve ritorno  per essere chiamato 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 sopra riportata registrerà anche 16, perché bar può ancora fare riferimento a x e tmp, anche se non è più direttamente all'interno dell'oscilloscopio.

Tuttavia, da allora tmp è ancora in giro all'interno barLa chiusura, è anche in aumento. 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 al stesso   x e lo stesso tmp, non fanno le loro copie.

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

D'altra parte, JavaScript usa sempre riferimenti quando si tratta di oggetti. Se dici, hai chiamato foocon un oggetto, la chiusura restituirà riferimento  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) aumenterà x.memb. Cosa non ci si potrebbe aspettare, è quello x si riferisce semplicemente allo stesso oggetto di age variabile! Dopo un paio di chiamate a bar, age.memb sarà 2! Questo riferimento è la base per perdite di memoria con oggetti HTML.


3804



PREFAZIONE: questa risposta è stata scritta quando la domanda era:

Come il vecchio Albert ha detto: "Se non riesci a spiegarlo a un bambino di sei anni, davvero non lo capisci da solo." Beh, ho cercato di spiegare le chiusure di JS a un amico di 27 anni e ho completamente fallito.

Qualcuno può considerare che io abbia 6 anni e sia stranamente interessato a quell'argomento?

Sono abbastanza sicuro di essere stato una delle poche persone che ha tentato di rispondere alla domanda iniziale letteralmente. Da allora, la domanda è mutata più volte, quindi la mia risposta potrebbe sembrare incredibilmente stupida e fuori luogo. Speriamo che l'idea generale della storia rimanga divertente per alcuni.


Sono un grande fan dell'analogia e della metafora quando spieghiamo concetti difficili, quindi lasciami provare la mia mano con una storia.

C'era una volta:

C'era una principessa ...

function princess() {

Ha vissuto in un mondo meraviglioso pieno di avventure. Incontrò il suo Principe Azzurro, cavalcò il suo mondo su un unicorno, combatté draghi, incontrò animali parlanti e molte altre cose fantastiche.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Ma avrebbe sempre dovuto tornare al suo noioso mondo di faccende e adulti.

    return {

E lei raccontava spesso la sua ultima incredibile avventura come principessa.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Ma tutto quello che vedrebbero è una bambina ...

var littleGirl = princess();

... raccontando storie di magia e fantasia.

littleGirl.story();

E anche se i grandi conoscevano le vere principesse, non avrebbero mai creduto negli unicorni o nei draghi perché non potevano mai vederli. Gli adulti dicevano che esistevano solo nell'immaginazione della bambina.

Ma noi conosciamo la vera verità; che la bambina con la principessa dentro ...

... è davvero una principessa con una bambina dentro.


2251



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.

Sopra 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 prende un sacchetto della spazzatura 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();

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

Ulteriori punti che spiegano perché le chiusure sono interessanti:

  • Ogni volta makeKitchen() viene chiamato, una nuova chiusura viene creata con il suo separato trashBags.
  • Il trashBags variabile è locale all'interno di ogni cucina e non è accessibile all'esterno, ma la funzione interna sul getTrashBagla proprietà non 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. Restituendo l'oggetto con il getTrashBag la funzione lo fa qui.

691



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 a racchiudi 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 del blog  (comprese le considerazioni su jQuery)


526



Le chiusure sono difficili da spiegare perché sono usate per fare del lavoro comportamentale che tutti si aspettano intuitivamente di lavorare comunque. Trovo il modo migliore per spiegarli (e il modo in cui io  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 l'ha fatto  sapere 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 plus5  trasportare  il suo scopo (o meglio lo scopo del genitore) in giro. Per di qua, x è ben definito ed è legato al valore 5.


444



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.  In effetti, la funzione di chiusura non ha bisogno di tornare affatto  in modo che la sua chiusura sia creata. 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, probabilmente è stata creata la chiusura della funzione di chiusura non appena viene chiamata la funzione di chiusura  poiché ogni funzione interna ha accesso a quella chiusura ogni volta che viene chiamata la funzione interna, prima o dopo la funzione di chiusura restituisce.
  • Una chiusura non fa riferimento a una copia del vecchi valori  di 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 qualsiasi funzione con nome  dichiarato 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.

342



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 compie 27 anni, guarda il 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");

330



UN chiusura  è molto simile a un oggetto. Viene istanziato ogni volta che si chiama una funzione.

Lo scopo di a chiusura  in JavaScript è lessicale, il che significa che tutto ciò che è contenuto nella funzione chiusura  appartiene a, ha accesso a qualsiasi variabile che è in esso.

Una variabile è contenuta nel chiusura  se tu

  1. assegnarlo con var foo=1; o
  2. Scrivi e basta var foo;

Se una funzione interna (una funzione contenuta all'interno di un'altra funzione) accede a tale variabile senza definirla nel suo ambito con var, modifica il contenuto della variabile nell'esterno chiusura .

UN chiusura  sopravvive al runtime della funzione che lo ha generato. Se altre funzioni lo rendono fuori dal Chiusura / portata in cui sono definiti (ad esempio come valori di ritorno), quelli continueranno a fare riferimento a ciò chiusura .

Esempio

 function example(closure) {
   // define somevariable to live in the closure of example
   var somevariable = 'unchanged';

   return {
     change_to: function(value) {
       somevariable = value;
     },
     log: function(value) {
       console.log('somevariable of closure %s is: %s',
         closure, somevariable);
     }
   }
 }

 closure_one = example('one');
 closure_two = example('two');

 closure_one.log();
 closure_two.log();
 closure_one.change_to('some new value');
 closure_one.log();
 closure_two.log();

Produzione

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

319



Ho scritto un post sul blog un po 'indietro spiegando chiusure. Ecco cosa ho detto sulle chiusure in termini di perché  ne vorresti uno

Le chiusure sono un modo per lasciare una funzione   avere variabili private persistenti  -   cioè, variabili che solo una   la funzione sa, dove può   tenere traccia delle informazioni dei tempi precedenti   che è stato eseguito.

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?


214



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 a make_calculator crea una nuova variabile locale n, che continua ad essere utilizzabile da quella calcolatrice add e multiply funzioni molto dopo make_calculator ritorna.

Se hai familiarità con i frame dello stack, questi calcolatori sembrano strani: come possono continuare ad accedere n dopo make_calculator ritorna? 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.

Funzioni interne come add e multiply, che accede alle variabili dichiarate in una funzione esterna ** , sono chiamati chiusure .

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



*  Ad esempio, copre tutti i punti contenuti nell'articolo "Chiusure per un vero manichino" un'altra risposta , eccetto l'esempio 6, che mostra semplicemente che le variabili possono essere utilizzate prima di essere dichiarate, un fatto piacevole da sapere ma completamente estraneo alle chiusure. Copre anche tutti i punti in la risposta accettata , eccetto per i punti (1) che funzionano copiano i loro argomenti in variabili locali (gli argomenti delle funzioni con nome), e (2) che i numeri di copia creano un nuovo numero, ma copiare un riferimento ad un 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 in questa risposta  ma un po 'più breve e meno astratto. Non copre il punto di questa risposta  o questo commento , che è JavaScript rende difficile collegare il attuale  valore 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 diversi sono nidificati, o anche nel contesto globale, come questa risposta  indica chiaramente.


199