Chiusure JavaScript vs. funzioni anonime


562

Un mio amico e io stiamo discutendo su cosa sia una chiusura in JS e cosa no. Vogliamo solo assicurarci di capirlo davvero correttamente.

Facciamo questo esempio. Abbiamo un ciclo di conteggio e vogliamo stampare la variabile del contatore sulla console in ritardo. Pertanto utilizziamo setTimeoute chiusure per acquisire il valore della variabile contatore per assicurarci che non venga stampato N volte il valore N.

La soluzione sbagliata senza chiusure o qualcosa vicino alle chiusure sarebbe:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

che ovviamente stampa 10 volte il valore di iafter the loop, ovvero 10.

Quindi il suo tentativo è stato:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

stampa da 0 a 9 come previsto.

Gli ho detto che non sta usando una chiusura per catturare i, ma insiste che lo è. Ho dimostrato che non usa le chiusure inserendo il corpo del ciclo for all'interno di un altro setTimeout(passando la sua funzione anonima a setTimeout), stampando di nuovo 10 volte 10. Lo stesso vale se memorizzo la sua funzione in a vare la eseguo dopo il ciclo, stampando anche 10 volte 10. Quindi il mio argomento è che non cattura davvero il valore dii , rendendo la sua versione non una chiusura.

Il mio tentativo è stato:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

Quindi catturo i(chiamato i2all'interno della chiusura), ma ora restituisco un'altra funzione e la passo. Nel mio caso, la funzione passata a setTimeout cattura davvero i.

Ora chi sta usando le chiusure e chi no?

Si noti che entrambe le soluzioni stampano in ritardo da 0 a 9 sulla console, quindi risolvono il problema originale, ma vogliamo capire quale di queste due soluzioni utilizza le chiusure per raggiungere questo obiettivo.



1
@leemes: guarda la mia modifica ninja per un secondo link.
Blender,

2
abbiamo appena fatto un accordo: colui che ha ragione otterrà i punti SO relativi a questa domanda
brillout,

1
@leemes - Entrambi state usando le chiusure. Entrambi avete realizzato due funzioni: una funzione esterna e una funzione interna; ed entrambe le tue funzioni interne sono chiusure. Tutte le tue funzioni sono lambda ( funzioni anonime ). Leggi la mia risposta per i dettagli.
Aadit M Shah,

1
@blesh - Non ho idea di cosa sia una chiusura modificata. Vedo che il tuo link punta al codice C #. Le chiusure modificate sono supportate da JavaScript?
Aadit M Shah

Risposte:


650

Nota dell'editore: tutte le funzioni in JavaScript sono chiusure come spiegato in questo post . Tuttavia, ci interessa solo identificare un sottoinsieme di queste funzioni che sono interessanti da un punto di vista teorico. D'ora in poi qualsiasi riferimento alla parola chiusura farà riferimento a questo sottoinsieme di funzioni se non diversamente indicato.

Una semplice spiegazione per le chiusure:

  1. Prendi una funzione. Chiamiamolo F.
  2. Elencare tutte le variabili di F.
  3. Le variabili possono essere di due tipi:
    1. Variabili locali (variabili associate)
    2. Variabili non locali (variabili libere)
  4. Se F non ha variabili libere, non può essere una chiusura.
  5. Se F ha delle variabili libere (che sono definite in a ambito padre di F), allora:
    1. Ci deve essere solo un ambito padre di F a cui a è associata variabile libera.
    2. Se si fa riferimento a F dall'esterno di tale ambito padre, diventa una chiusura per quella variabile libera.
    3. Quella variabile libera è chiamata un valore superiore della chiusura F.

Ora usiamo questo per capire chi usa chiusure e chi no (per motivi di spiegazione ho chiamato le funzioni):

Caso 1: il programma del tuo amico

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

Nel programma sopra ci sono due funzioni: feg . Vediamo se sono chiusure:

Per f:

  1. Elencare le variabili:
    1. i2è una variabile locale .
    2. iè una variabile libera .
    3. setTimeoutè una variabile libera .
    4. gè una variabile locale .
    5. consoleè una variabile libera .
  2. Trova l'ambito padre a cui è associata ciascuna variabile libera:
    1. iè legato all'ambito globale.
    2. setTimeoutè legato all'ambito globale.
    3. consoleè legato all'ambito globale.
  3. In quale ambito si fa riferimento alla funzione ? L' ambito globale .
    1. Quindi inon è chiuso da f.
    2. Quindi setTimeoutnon è chiuso da f.
    3. Quindi consolenon è chiuso da f.

Pertanto la funzione fnon è una chiusura.

Per g:

  1. Elencare le variabili:
    1. consoleè una variabile libera .
    2. i2è una variabile libera .
  2. Trova l'ambito padre a cui è associata ciascuna variabile libera:
    1. consoleè legato all'ambito globale.
    2. i2è legato allo scopo di f.
  3. In quale ambito si fa riferimento alla funzione ? Lo scopo disetTimeout .
    1. Quindi consolenon è chiuso da g.
    2. Quindi i2è chiuso da g.

Quindi la funzione gè una chiusura per la variabile libera i2(che è un valore in rialzo per g) quando viene referenziata dall'interno setTimeout.

Male per te: il tuo amico sta usando una chiusura. La funzione interna è una chiusura.

Caso 2: il tuo programma

for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

Nel programma sopra ci sono due funzioni: feg . Vediamo se sono chiusure:

Per f:

  1. Elencare le variabili:
    1. i2è una variabile locale .
    2. gè una variabile locale .
    3. consoleè una variabile libera .
  2. Trova l'ambito padre a cui è associata ciascuna variabile libera:
    1. consoleè legato all'ambito globale.
  3. In quale ambito si fa riferimento alla funzione ? L' ambito globale .
    1. Quindi consolenon è chiuso da f.

Pertanto la funzione fnon è una chiusura.

Per g:

  1. Elencare le variabili:
    1. consoleè una variabile libera .
    2. i2è una variabile libera .
  2. Trova l'ambito padre a cui è associata ciascuna variabile libera:
    1. consoleè legato all'ambito globale.
    2. i2è legato allo scopo di f.
  3. In quale ambito si fa riferimento alla funzione ? Lo scopo disetTimeout .
    1. Quindi consolenon è chiuso da g.
    2. Quindi i2è chiuso da g.

Quindi la funzione gè una chiusura per la variabile libera i2(che è un valore in rialzo per g) quando viene referenziata dall'interno setTimeout.

Buon per te: stai usando una chiusura. La funzione interna è una chiusura.

Quindi sia tu che il tuo amico state usando le chiusure. Smettere di litigare. Spero di aver chiarito il concetto di chiusure e come identificarle per entrambi.

Modifica: una semplice spiegazione del perché tutte le chiusure di funzioni (credits @Peter):

Per prima cosa consideriamo il seguente programma (è il controllo ):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

  1. Sappiamo che entrambi lexicalScopee regularFunctionnon sono chiusure dalla definizione sopra .
  2. Quando eseguiamo il programma ci aspettiamo message di essere avvisati perché regularFunction non è una chiusura (cioè ha accesso a tutte le variabili nel suo ambito genitore - incluso message).
  3. Quando eseguiamo il programma osserviamo che messageviene effettivamente avvisato.

Quindi consideriamo il seguente programma (è l' alternativa ):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. Sappiamo che closureFunctionè solo una chiusura dalla definizione di cui sopra .
  2. Quando eseguiamo il programma ci aspettiamo di message non essere avvisati perché closureFunction è una chiusura (cioè ha accesso a tutte le sue variabili non locali al momento della creazione della funzione ( vedi questa risposta ) - questo non include message).
  3. Quando eseguiamo il programma osserviamo che messageviene effettivamente avvisato.

Cosa deduciamo da questo?

  1. Gli interpreti JavaScript non trattano le chiusure in modo diverso dal modo in cui trattano altre funzioni.
  2. Ogni funzione porta con sé la sua catena di portata . Le chiusure non hanno un ambiente di riferimento separato .
  3. Una chiusura è proprio come ogni altra funzione. Le chiamiamo semplicemente chiusure quando vengono citate in un ambito al di fuori dell'ambito a cui appartengono perché questo è un caso interessante.

40
Accettato perché vai molto nei dettagli, spiegando molto bene cosa sta succedendo. E infine, ora ho capito meglio cos'è una chiusura, o meglio detto: come funziona l'associazione variabile in JS.
Rimane il

3
Nel caso 1, dici che gfunziona nell'ambito di setTimeout, ma nel caso 2 lo dicif funziona nell'ambito globale. Sono entrambi all'interno di setTimeout, quindi qual è la differenza?
rosscj2533,

9
Potresti per favore dichiarare le tue fonti per questo? Non ho mai visto una definizione in cui una funzione potrebbe essere una chiusura se chiamata in un ambito ma non in un altro. Quindi, questa definizione sembra un sottoinsieme della definizione più generale a cui sono abituato (vedi la risposta di kev ) dove una chiusura è una chiusura è una chiusura indipendentemente dall'ambito che viene chiamata, o anche se non viene mai chiamata!
Briguy37,

11
@AaditMShah Sono d'accordo con te su cosa sia una chiusura, ma tu parli come se ci fosse una differenza tra funzioni regolari e chiusure in JavaScript. Non c'è differenza; internamente ogni funzione porterà con sé un riferimento alla particolare catena di portata in cui è stata creata. Il motore JS non lo considera un caso diverso. Non è necessario un elenco di controllo complicato; sappi solo che ogni oggetto funzione ha portata lessicale. Il fatto che variabili / proprietà siano disponibili a livello globale non rende la funzione meno una chiusura (è solo un caso inutile).
Peter,

13
@Peter - Sai una cosa, hai ragione. Non c'è differenza tra una funzione regolare e una chiusura. Ho eseguito un test per dimostrarlo e questo risulta a tuo favore: ecco il controllo ed ecco l' alternativa . Quello che dici ha senso. L'interprete JavaScript deve eseguire una contabilità speciale per le chiusure. Sono semplicemente sottoprodotti di un linguaggio con ambito lessicale con funzioni di prima classe. La mia conoscenza era limitata a ciò che leggevo (che era falso). Grazie per avermi corretto. Aggiornerò la mia risposta per riflettere lo stesso.
Aadit M Shah,

96

Secondo la closuredefinizione:

Una "chiusura" è un'espressione (in genere una funzione) che può avere variabili libere insieme a un ambiente che lega tali variabili (che "chiude" l'espressione).

Si sta utilizzando closurese si definisce una funzione che utilizza una variabile definita al di fuori della funzione. (chiamiamo la variabile una variabile libera ).
Tutti usano closure(anche nel primo esempio).


1
In che modo la terza versione utilizza una variabile definita all'esterno della funzione?
Jon,

1
@Jon l'uso della funzione restituita i2definita all'esterno.
kev

1
@kev Stai usando la chiusura se definisci una funzione che usa una variabile che è definita al di fuori della funzione ...... quindi nel "Caso 1: il programma del tuo amico" di "Aadit M Shah" la risposta è "funzione f" una chiusura? utilizza la i (variabile definita al di fuori della funzione). l'ambito globale fa riferimento a un determinante?
internati-in


54

In poche parole Javascript Closures permettono una funzione per accedere a una variabile che viene dichiarata in una funzione lessicale-genitore .

Vediamo una spiegazione più dettagliata. Per comprendere le chiusure è importante comprendere come le variabili degli ambiti JavaScript.

Scopes

In JavaScript gli ambiti sono definiti con funzioni. Ogni funzione definisce un nuovo ambito.

Considera il seguente esempio;

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

chiamando f stampe

hello
hello
2
Am I Accessible?

Consideriamo ora il caso in cui abbiamo una funzione gdefinita all'interno di un'altra funzione f.

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

Chiameremo fil genitore lessicale di g. Come spiegato prima, ora abbiamo 2 ambiti; l'ambito fe l'ambitog .

Ma un ambito è "all'interno" dell'altro ambito, quindi l'ambito della funzione figlio fa parte dell'ambito della funzione padre? Cosa succede con le variabili dichiarate nell'ambito della funzione genitore; sarò in grado di accedervi dall'ambito della funzione figlio? È esattamente qui che entrano in gioco le chiusure.

chiusure

In JavaScript la funzione gnon può solo accedere a tutte le variabili dichiarate nell'ambito gma anche a tutte le variabili dichiarate nell'ambito della funzione genitore f.

Valuta di seguire;

function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

chiamando f stampe

hello
undefined

Diamo un'occhiata alla linea console.log(foo);. A questo punto siamo nell'ambito ge cerchiamo di accedere alla variabile foodichiarata nell'ambito f. Ma come detto prima possiamo accedere a qualsiasi variabile dichiarata in una funzione parentale lessicale che è il caso qui; gè il genitore lessicale di f. Pertanto helloviene stampato.
Vediamo ora la linea console.log(bar);. A questo punto siamo nell'ambito fe cerchiamo di accedere alla variabile bardichiarata nell'ambito g. barnon è dichiarato nell'ambito corrente e la funzione gnon è il genitore di f, quindi barnon è definita

In realtà possiamo anche accedere alle variabili dichiarate nell'ambito di una funzione lessicale "nonna". Pertanto, se ci fosse una funzione hdefinita all'interno della funzioneg

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

quindi hsarebbe in grado di accedere a tutte le variabili dichiarate nel campo di applicazione della funzione h, ge f. Questo viene fatto con le chiusure . Nelle chiusure JavaScript ci consente di accedere a qualsiasi variabile dichiarata nella funzione lessicale parent, nella funzione lessicale grand parent, nella funzione lessical grand-grand parent, ecc. Ciò può essere visto come una catena di scope ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... fino all'ultima funzione padre che non ha genitori lessicali.

L'oggetto finestra

In realtà la catena non si ferma all'ultima funzione genitore. C'è un altro ambito speciale; il ambito globale . Ogni variabile non dichiarata in una funzione è considerata dichiarata nell'ambito globale. L'ambito globale ha due specialità;

  • ogni variabile dichiarata nell'ambito globale è accessibile ovunque
  • le variabili dichiarate nell'ambito globale corrispondono alle proprietà di window dell'oggetto.

Pertanto ci sono esattamente due modi per dichiarare una variabile foo nell'ambito globale; o non dichiarandolo in una funzione o impostando la proprietàfoo dell'oggetto finestra.

Entrambi i tentativi utilizzano chiusure

Ora che hai letto una spiegazione più dettagliata, ora può essere evidente che entrambe le soluzioni utilizzano chiusure. Ma per essere sicuri, facciamo una prova.

Creiamo un nuovo linguaggio di programmazione; JavaScript-No-chiusura. Come suggerisce il nome, JavaScript-No-Closure è identico a JavaScript, tranne per il fatto che non supporta le chiusure.

In altre parole;

var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

Bene, vediamo cosa succede con la prima soluzione con JavaScript-No-Closure;

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}

pertanto questo verrà stampato undefined10 volte in JavaScript-No-Closure.

Quindi la prima soluzione utilizza la chiusura.

Diamo un'occhiata alla seconda soluzione;

for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}

pertanto questo verrà stampato undefined10 volte in JavaScript-No-Closure.

Entrambe le soluzioni utilizzano chiusure.

Modifica: si presume che questi 3 frammenti di codice non siano definiti nell'ambito globale. Altrimenti le variabili fooe isarebbero legate windowall'oggetto e quindi accessibili attraverso l' windowoggetto sia in JavaScript che in JavaScript-No-Closure.


Perché dovrebbe iessere indefinito? Ti basta fare riferimento all'ambito padre, che è ancora valido se non ci sono chiusure.
Sembra il

per lo stesso motivo per cui foo non è definito in JavaScript-No-Closure. <code> i </code> non è definito in JavaScript grazie a una funzione in JavaScript che consente di accedere alle variabili definite nel parent lessicale. Questa funzione si chiama chiusura.
Brillout,

Non hai capito la differenza tra il riferimento a variabili già definite e variabili libere . Nelle chiusure, definiamo variabili libere che devono essere vincolate nel contesto esterno. Nel codice, è sufficiente impostare i2 a inel momento in cui si definisce la funzione. Questo iNON rende una variabile libera. Tuttavia, consideriamo la tua funzione una chiusura, ma senza alcuna variabile libera, questo è il punto.
Sembra il

2
@leemes, sono d'accordo. E rispetto alla risposta accettata, questo non mostra veramente cosa sta succedendo. :)
Abel,

3
penso che questa sia la risposta migliore, spiegando le chiusure in generale e semplicemente e poi siamo andati nel caso d'uso specifico. Grazie!
Tim Peterson,

22

Non sono mai stato contento del modo in cui qualcuno lo spiega.

La chiave per comprendere le chiusure è capire come sarebbe JS senza chiusure.

Senza chiusure, ciò genererebbe un errore

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

Una volta che outerFunc è tornato in una versione immaginaria di JavaScript disabilitato per la chiusura, il riferimento a outerVar verrebbe raccolto in modo inutile e non lascerebbe nulla lì a cui fare riferimento la funzione interna.

Le chiusure sono essenzialmente le regole speciali che attivano e rendono possibile l'esistenza di quei vars quando una funzione interna fa riferimento alle variabili di una funzione esterna. Con le chiusure, le variabili a cui si fa riferimento vengono mantenute anche dopo che la funzione esterna è stata eseguita o 'chiusa' se ciò aiuta a ricordare il punto.

Anche con le chiusure, il ciclo di vita dei var locali in una funzione senza funzioni interne che fanno riferimento ai suoi locali funziona allo stesso modo di una versione senza chiusura. Al termine della funzione, i locali ricevono la spazzatura raccolta.

Una volta che hai un riferimento in una funzione interna a una variante esterna, tuttavia è come se uno stipite della porta venisse messo in mezzo alla raccolta dei rifiuti per quei vars referenziati.

Un modo forse più preciso di osservare le chiusure è che la funzione interna utilizza fondamentalmente l'ambito interno come propria base di riferimento.

Ma il contesto a cui si fa riferimento è in effetti persistente, non come un'istantanea. L'attivazione ripetuta di una funzione interna restituita che continua a incrementare e registrare la var locale di una funzione esterna continuerà a segnalare valori più alti.

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

Hai ragione riguardo allo 'snapshot' (penso, ti riferisci alla mia risposta) da quello. Stavo cercando una parola che indicasse il comportamento. Nel tuo esempio, può essere visto come una costruzione di chiusura 'hotlink'. Quando si rileva la chiusura come parametro nella funzione interna, si potrebbe affermare che si comporta come una "istantanea". Ma sono d'accordo, le parole abusate aggiungono solo confusione all'argomento. Se hai qualche suggerimento a riguardo, aggiornerò la mia risposta.
Andries,

Potrebbe essere utile una spiegazione se assegni alla funzione interna una funzione denominata.
Phillip Senn,

Senza chiusure, si otterrebbe un errore perché si sta tentando di utilizzare una variabile che non esiste.
Juan Mendes,

Hmm ... buon punto. Il riferimento a una var indefinita non ha mai generato un errore poiché alla fine avrebbe cercato una proprietà sull'oggetto globale o sto confondendo con l'assegnazione a var indefiniti?
Erik Reppen,

17

Entrambi state usando le chiusure.

Vado con la definizione di Wikipedia qui:

Nell'informatica, una chiusura (anche chiusura lessicale o chiusura di funzioni) è una funzione o un riferimento a una funzione insieme a un ambiente di riferimento: una tabella che memorizza un riferimento a ciascuna delle variabili non locali (chiamate anche variabili libere) di quella funzione . Una chiusura, a differenza di un semplice puntatore a funzione, consente a una funzione di accedere a tali variabili non locali anche se invocata al di fuori del suo immediato ambito lessicale.

Il tentativo del tuo amico usa chiaramente la variabile i, che non è locale, prendendo il suo valore e facendo una copia da archiviare nel locale i2.

Il tuo tentativo passa i(che nel sito di chiamata rientra nell'ambito) a una funzione anonima come argomento. Finora non si tratta di una chiusura, ma poi quella funzione restituisce un'altra funzione che fa riferimento alla stessa i2. Poiché all'interno della funzione anonima interna i2non è un locale, questo crea una chiusura.


Sì, ma penso che il punto sia come lo sta facendo. Copia solo iin i2, quindi definisce un po 'di logica ed esegue questa funzione. Se non lo eseguissi immediatamente, ma lo memorizzassi in un var e lo eseguissi dopo il ciclo, ne stamperebbe 10, no? Quindi non ha catturato i.
Sembra il

6
@leemes: ha catturato ibene. Il comportamento che stai descrivendo non è il risultato della chiusura rispetto alla mancata chiusura; è il risultato della modifica della variabile chiusa nel frattempo. Stai facendo la stessa cosa usando una sintassi diversa chiamando immediatamente una funzione e passando icome argomento (che copia il suo valore attuale sul posto). Se metti il ​​tuo setTimeoutdentro l'altro setTimeout, accadrà la stessa cosa.
Jon,

13

Sia tu che il tuo amico utilizzate le chiusure:

Una chiusura è un tipo speciale di oggetto che combina due cose: una funzione e l'ambiente in cui è stata creata quella funzione. L'ambiente è costituito da tutte le variabili locali che erano nell'ambito al momento della creazione della chiusura.

MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

Nella funzione del codice del tuo amico function(){ console.log(i2); }definita all'interno della chiusura della funzione anonima function(){ var i2 = i; ...e può leggere / scrivere variabili localii2 .

Nel tuo codice funzione function(){ console.log(i2); }definita all'interno della chiusura della funzione function(i2){ return ...e in grado di leggere / scrivere locale preziosoi2 (dichiarato in questo caso come parametro).

In entrambi i casi la funzione è function(){ console.log(i2); }passata asetTimeout .

Un altro equivalente (ma con meno utilizzo della memoria) è:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

1
Non vedo perché la tua soluzione rispetto alla soluzione del mio amico "è più veloce e con meno utilizzo della memoria", potresti elaborare?
Brillout,

3
Nella tua soluzione crei 20 oggetti funzione (2 oggetti su ciascun loop: 2x10 = 20). Stesso risultato in soluzione della tua amicizia. Nella "mia" soluzione vengono creati solo 11 oggetti funzione: 1 prima per loop e 10 "dentro" - 1 + 1x10 = 11. Di conseguenza, meno utilizzo della memoria e aumento della velocità.
Andrew D.

1
In teoria, sarebbe vero. In pratica, anche: Vedi questo benchmark JSPerf
Rob W

10

Chiusura

Una chiusura non è una funzione e non un'espressione. Deve essere visto come una sorta di "istantanea" dalle variabili utilizzate al di fuori del campo di funzioni e utilizzata all'interno della funzione. Grammaticamente, si dovrebbe dire: "prendere la chiusura delle variabili".

Ancora una volta, in altre parole: una chiusura è una copia del contesto rilevante delle variabili da cui dipende la funzione.

Ancora una volta (naif): una chiusura ha accesso a variabili che non vengono passate come parametro.

Ricorda che questi concetti funzionali dipendono fortemente dal linguaggio / ambiente di programmazione che usi. In JavaScript, la chiusura dipende dall'ambito lessicale (che è vero nella maggior parte dei linguaggi c).

Quindi, la restituzione di una funzione sta principalmente restituendo una funzione anonima / senza nome. Quando la funzione accede alle variabili, non passate come parametro e nel suo ambito (lessicale), è stata presa una chiusura.

Quindi, per quanto riguarda i tuoi esempi:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

Tutti usano chiusure. Non confondere il punto di esecuzione con le chiusure. Se l'istantanea delle chiusure viene presa nel momento sbagliato, i valori potrebbero essere inattesi, ma sicuramente viene presa una chiusura!



10

Diamo un'occhiata a entrambi i modi:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

Dichiara ed esegue immediatamente una funzione anonima che viene eseguita setTimeout()nel proprio contesto. Il valore corrente di iviene preservato eseguendo prima una copia i2; funziona a causa dell'esecuzione immediata.

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

Dichiara un contesto di esecuzione per la funzione interna in cui iviene conservato il valore corrente di i2; questo approccio utilizza anche l'esecuzione immediata per preservare il valore.

Importante

Va detto che la semantica della corsa NON è la stessa tra entrambi gli approcci; viene passata la tua funzione interiore setTimeout()mentre la sua funzione interiore chiamasetTimeout() se stessa.

Avvolgere entrambi i codici all'interno di un altro setTimeout()non dimostra che solo il secondo approccio utilizza le chiusure, non c'è proprio la stessa cosa per cominciare.

Conclusione

Entrambi i metodi usano chiusure, quindi si riduce al gusto personale; il secondo approccio è più semplice "spostarsi" o generalizzare.


Penso che la differenza sia: la sua soluzione (1 °) è catturare per riferimento, la mia (2 °) è catturare per valore. In questo caso non fa differenza, ma se dovessi mettere l'esecuzione in un altro setTimeout, vedremmo che la sua soluzione ha il problema che utilizza quindi il valore finale di io, non quello attuale, mentre il mio davanzale usa il valore corrente (poiché acquisito dal valore).
Sembra il

@leemes Entrambi catturate allo stesso modo; passare una variabile tramite argomento di funzione o assegnazione è la stessa cosa ... potresti aggiungere alla tua domanda come potresti concludere l'esecuzione in un'altra setTimeout()?
Ja͢ck,

fammi dare un'occhiata ... Volevo dimostrare che l'oggetto funzione può essere passato in giro e la variabile originale ipuò essere cambiata senza influenzare ciò che la funzione dovrebbe stampare, non dipende da dove o quando lo eseguiamo.
Sembra il

Aspetta, non hai passato una funzione a (l'esterno) setTimeout. Rimuovi quelli (), passando così una funzione, e vedi 10 volte l'output 10.
Sembra il

@leemes Come accennato in precedenza, ()è esattamente ciò che fa funzionare il suo codice, proprio come il tuo (i); non hai semplicemente racchiuso il suo codice, hai apportato modifiche ad esso .. quindi non puoi più effettuare un confronto valido.
Ja͢ck,

8

L'ho scritto qualche tempo fa per ricordarmi cos'è una chiusura e come funziona in JS.

Una chiusura è una funzione che, quando viene chiamata, utilizza l'ambito in cui è stata dichiarata, non l'ambito in cui è stata chiamata. In javaScript, tutte le funzioni si comportano così. I valori delle variabili in un ambito persistono finché esiste una funzione che li punta ancora. L'eccezione alla regola è 'this', che si riferisce all'oggetto in cui si trova la funzione quando viene chiamata.

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 

6

Dopo un'attenta ispezione, sembra che entrambi stiate usando la chiusura.

Nel caso dei tuoi amici, iaccedi alla funzione anonima 1 e i2accedi alla funzione anonima 2 doveconsole.log è presente.

Nel tuo caso stai accedendo alla i2funzione anonima dove console.logè presente. Aggiungi una debugger;dichiarazione prima console.loge negli strumenti di sviluppo di Chrome in "Scope variabile" indicherà in quale ambito si trova la variabile.


2
La sezione "Chiusura" nel pannello di destra viene utilizzata perché non esiste un nome più specifico. "Locale" è un'indicazione più forte di "Chiusura".
Rob W,


4

Considera quanto segue. Questo crea e ricrea una funzione fche si chiude i, ma differenti !:

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

mentre il seguente si chiude su "una" funzione "stessa"
(loro stessi! lo snippet dopo questo utilizza un singolo referente f)

for(var i = 0; i < 10; i++) {
    setTimeout( new Function('console.log('+i+')'),  1000 );
}

o per essere più espliciti:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

NB. l'ultima definizione di fè function(){ console.log(9) } prima 0 viene stampato.

Avvertimento! Il concetto di chiusura può essere una distrazione coercitiva dall'essenza della programmazione elementare:

for(var i = 0; i < 10; i++) {     setTimeout( 'console.log('+i+')',  1000 );      }

x-refs .:
Come funzionano le chiusure JavaScript?
Spiegazione delle chiusure Javascript Una chiusura
(JS) richiede una funzione all'interno di una funzione
Come comprendere le chiusure in Javascript?
Javascript confusione variabile locale e globale


frammenti provati per la prima volta - non so come controllare - Run' only was desired - not sure how to remove the Copia`
ekim

-1

Vorrei condividere il mio esempio e una spiegazione sulle chiusure. Ho fatto un esempio di pitone e due figure per dimostrare gli stati dello stack.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

L'output di questo codice sarebbe il seguente:

*****      hello      #####

      good bye!    ♥♥♥

Ecco due figure per mostrare le pile e la chiusura attaccata all'oggetto funzione.

quando la funzione viene restituita dal produttore

quando la funzione viene chiamata in seguito

Quando la funzione viene chiamata tramite un parametro o una variabile non locale, il codice necessita di associazioni di variabili locali come margin_top, padding e a, b, n. Al fine di garantire il funzionamento del codice funzione, il frame dello stack della funzione maker che è stato rimosso molto tempo fa dovrebbe essere accessibile, di cui è stato eseguito il backup nella chiusura che possiamo trovare insieme all'oggetto messaggio funzione.


Vorrei rimuovere questa risposta. Mi sono reso conto che la domanda non riguarda la chiusura, quindi vorrei spostarla sull'altra domanda.
Eunjung Lee,

2
Credo che tu abbia la possibilità di eliminare i tuoi contenuti. Fai clic sul deletelink sotto la risposta.
Rory McCrossan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.