2009-03-13 7 views
51

Sono molto perplesso su questo codice:JavaScript non supporta le chiusure con variabili locali?

var closures = []; 
function create() { 
    for (var i = 0; i < 5; i++) { 
    closures[i] = function() { 
     alert("i = " + i); 
    }; 
    } 
} 

function run() { 
    for (var i = 0; i < 5; i++) { 
    closures[i](); 
    } 
} 

create(); 
run(); 

Dalla mia comprensione dovrebbe stampare 0,1,2,3,4 (non è questo il concetto di chiusure?).

Invece stampa 5,5,5,5,5.

Ho provato Rhino e Firefox.

Qualcuno potrebbe spiegarmi questo comportamento? Thx in anticipo.

risposta

56

fisso risposta di Jon con l'aggiunta di un ulteriore funzione anonima:

function create() { 
    for (var i = 0; i < 5; i++) { 
    closures[i] = (function(tmp) { 
     return function() { 
      alert("i = " + tmp); 
     }; 
    })(i); 
    } 
} 

La spiegazione è che gli ambiti JavaScript sono a livello di funzione, non a livello di blocco, e la creazione di una chiusura significa semplicemente che l'ambito che racchiude viene aggiunto all'ambiente lessicale della funzione chiusa.

Dopo che il ciclo termina, la variabile a livello di funzione i ha il valore 5, ed è ciò che la funzione interna "vede".


Come nota a margine: è necessario fare attenzione alla creazione di oggetti funzione non necessari, in modo esplicito nei cicli; è inefficiente e, se sono coinvolti oggetti DOM, è facile creare riferimenti circolari e quindi introdurre perdite di memoria in Internet Explorer.

6

Sì, le chiusure funzionano qui. Ogni volta che esegui il loop della funzione che stai creando, prendi lo i. Ogni funzione creata condivide lo stesso i. Il problema che si sta verificando è che poiché tutti condividono lo stesso i, condividono anche il valore finale di i poiché è la stessa variabile acquisita.

Edit:This article da Sig Skeet spiega chiusure a una certa profondità e risolve questo problema, in particolare, in un modo che è molto più informativo allora ho qui. Tuttavia, prestare attenzione al modo in cui le chiusure di handle di Javascript e C# presentano alcune sottili differenze. Passa alla sezione chiamata "Confronto tra strategie di acquisizione: complessità e potenza" per la sua spiegazione su questo argomento.

+0

Quindi quale sarebbe la sua soluzione (sono curioso anche adesso)? –

+0

La risposta di Jon ha la soluzione. –

+0

È un bell'articolo, ma sembra che ci siano alcune differenze nel modo in cui le chiusure sono implementate tra C# e Javascript. Questo rende l'articolo non così utile per quanto riguarda la domanda dell'OP. –

9

Penso che questo potrebbe essere quello che vuoi:

var closures = []; 

function createClosure(i) { 
    closures[i] = function() { 
     alert("i = " + i); 
    }; 
} 

function create() { 
    for (var i = 0; i < 5; i++) { 
     createClosure(i); 
    } 
} 
-1

Ecco cosa si dovrebbe fare per ottenere il risultato:

<script> 
var closures = []; 
function create() { 
    for (var i = 0; i < 5; i++) { 
     closures[i] = function(number) {  
     alert("i = " + number); 
     }; 
    } 
} 
function run() { 
    for (var i = 0; i < 5; i++) { 
     closures[i](i); 
    } 
} 
create(); 
run(); 
</script> 
+3

Come mai questo è un esempio di chiusure? In pratica stai semplicemente memorizzando le funzioni nella matrice, quindi esplicitamente fornendo 'i' attraverso gli argomenti della funzione. –

8

La soluzione è quella di avere un lambda self-executing avvolgendo il spinta matrice. Tu passi anche io come argomento a quel lambda. Il valore dei all'interno del lambda auto-esecuzione consente ombra il valore dei originale e tutto funzionerà come previsto:

function create() { 
    for (var i = 0; i < 5; i++) (function(i) { 
     closures[i] = function() { 
      alert("i = " + i); 
     }; 
    })(i); 
} 

Un'altra soluzione potrebbe essere quella di creare un'altra chiusura che cattura il valore corretto di i e cessionari ad un'altra variabile che "rimanere intrappolati" nel lambda finale:

function create() { 
    for (var i = 0; i < 5; i++) (function() { 
     var x = i; 

     closures.push(function() { 
      alert("i = " + x); 
     }); 
    })(); 
} 
+0

Per rendere la prima implementazione più chiara e comprensibile, è possibile utilizzare un nome parametro diverso da I alla funzione interna! –

+0

@Chetan Sastry, non ero esattamente dopo. Come puoi vedere, anche il posizionamento del lambda autoeseguente è strano. Come se all'inizio non ci fossero problemi. –

3

John Resig's Learning Advanced JavaScript spiega questo e altro. È una presentazione interattiva che spiega molto su JavaScript, e gli esempi sono divertenti da leggere ed eseguire.

Ha un capitolo sulle chiusure e this example sembra molto simile al tuo.

Ecco l'esempio rotta:

var count = 0; 
for (var i = 0; i < 4; i++) { 
    setTimeout(function(){ 
    assert(i == count++, "Check the value of i."); 
    }, i * 200); 
} 

E la correzione:

var count = 0; 
for (var i = 0; i < 4; i++) (function(i){ 
    setTimeout(function(){ 
    assert(i == count++, "Check the value of i."); 
    }, i * 200); 
})(i); 
1

Basta definire una funzione interna, o assegnandolo ad una variabile:

closures[i] = function() {... 

non crea una copia privata dell'intero contesto di esecuzione. Il contesto non viene copiato finché la funzione esterna più vicina non è in uscita da (a quel punto quelle variabili esterne potrebbero essere raccolte inutilmente, quindi è meglio prendere una copia).

Questo è il motivo per cui è possibile eseguire il wrapping di un'altra funzione attorno alla funzione interna: il middle man esegue ed esce, richiamando la funzione più interna per salvare la propria copia dello stack.