2015-07-08 42 views
22

Questo codice tronchi 6, 6 volte:Perché lasciare e le associazioni var si comportano diversamente usando la funzione setTimeout?

(function timer() { 
    for (var i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

ma questo codice ...

(function timer() { 
    for (let i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

... registra il seguente risultato:

0 
1 
2 
3 
4 
5 

Perché?

E 'perché let si collega allo scope interno ogni elemento in modo diverso e var mantiene l'ultimo valore di i?

+3

Vedere qui - http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword – eithed

+0

Vale la pena notare che ci sono differenze tra la vecchia implementazione di Mozilla di "let' e la nuova ES2015 versione. Tuttavia, per le specifiche di questa domanda, il dupe risponde perfettamente. –

+3

Non è un duplicato secondo me. Ogni volta che qualcuno chiede as o var, non possiamo indicarle una risposta molto generale. Si tratta in particolare di setTimeout(), che crea una "chiusura all'interno di un loop" - uno scenario problematico di sollevamento delle variabili comuni - la risposta e l'esempio di seguito non sono dettagliati nella risposta accettata duplicata collegata – Ryan

risposta

18

Con var si dispone di un insieme di funzioni, e solo uno condiviso vincolante per tutte le vostre iterazioni del ciclo - cioè la i in ogni callback setTimeout significa stesso variabile che finalmente è uguale a 6 dopo il ciclo l'iterazione finisce.

Con let si ha un campo di applicazione blocco e quando viene utilizzato nel ciclo for si ottiene un nuovo vincolante per ogni iterazione - ossia la i in ogni callback setTimeout mezzi un diverso variabile, ciascuno dei quali ha un valore diverso : il primo è 0, il prossimo è 1 ecc

Quindi questo:

(function timer() { 
    for (let i = 0; i <= 5; i++) { 
    setTimeout(function clog() { console.log(i); }, i * 1000); 
    } 
})(); 

è equivalente a questo utilizzando solo var:

(function timer() { 
    for (var j = 0; j <= 5; j++) { 
    (function() { 
     var i = j; 
     setTimeout(function clog() { console.log(i); }, i * 1000); 
    }()); 
    } 
})(); 

utilizzando l'espressione di funzione immediatamente invocata per utilizzare l'ambito di funzione in un modo simile a come l'ambito di blocco funziona nell'esempio con let.

Potrebbe essere scritto più breve senza utilizzare il nome j, ma forse non sarebbe più chiaro:

(function timer() { 
    for (var i = 0; i <= 5; i++) { 
    (function (i) { 
     setTimeout(function clog() { console.log(i); }, i * 1000); 
    }(i)); 
    } 
})(); 

E anche più breve con funzioni di direzione:

(() => { 
    for (var i = 0; i <= 5; i++) { 
    (i => setTimeout(() => console.log(i), i * 1000))(i); 
    } 
})(); 

(ma se si può usa le funzioni freccia, non c'è motivo di usare var.)

Ecco come Babel.js traduce il tuo esempio con.210 per l'esecuzione in ambienti in cui non è disponibile let:

"use strict"; 

(function timer() { 
    var _loop = function (i) { 
    setTimeout(function clog() { 
     console.log(i); 
    }, i * 1000); 
    }; 

    for (var i = 0; i <= 5; i++) { 
    _loop(i); 
    } 
})(); 

Grazie a Michael Geary per la pubblicazione il link al Babel.js nei commenti. Vedere il link nel commento per una demo dal vivo in cui è possibile modificare qualsiasi cosa nel codice e guardare la traduzione che si svolge immediatamente. È interessante vedere come vengono tradotte anche le altre funzionalità di ES6.

+2

Solo per aggiungere alla tua eccellente spiegazione, qui è una [traduzione del codice ES6 in ES5 fornita da babeljs.io] (http://babeljs.io/repl/#?experimental=true&evaluate=true&loose=false&spec=false&code= (function% 20timer()% 20% 7B% 0D% 0A% 20% 20for% 20 (lasciar% 20i% 20% 3D% 200% 3B% 20i% 20% 3C% 3D% 205% 3B% 20i% 2B% 2B)% 20% 7B% 0D% 0A% 20 % 20% 20% 20setTimeout (funzione% 20clog()% 20% 7B% 20console.log (i)% 3B% 20% 7D% 2C% 20i% 20% * 201000)% 3B% 0D% 0A% 20% 20% 7D% 0D% 0A% 7D)()% 3B). –

+0

@MichaelGeary Grazie per il link. Ho aggiunto la traduzione di Babel alla mia risposta. Grazie. – rsp

+0

Scoping non è rilevante qui, è fino alla nuova associazione creata ad ogni iterazione – seriousdev

2

Tecnicamente è come @rsp spiega nella sua eccellente risposta. Questo è il modo in cui mi piace capire che le cose funzionano sotto il cofano.Per il primo blocco di codice utilizzando var

(function timer() { 
    for (var i=0; i<=5; i++) { 
    setTimeout(function clog() {console.log(i)}, i*1000); 
    } 
})(); 

si può immaginare il compilatore va in questo modo all'interno del ciclo for

setTimeout(function clog() {console.log(i)}, i*1000); // first iteration, remember to call clog with value i after 1 sec 
setTimeout(function clog() {console.log(i)}, i*1000); // second iteration, remember to call clog with value i after 2 sec 
setTimeout(function clog() {console.log(i)}, i*1000); // third iteration, remember to call clog with value i after 3 sec 

e così via

dal i viene dichiarato utilizzando var, quando clog viene chiamato, il compilatore trova la variabile i nel blocco funzione più vicino che è timer e poiché abbiamo già raggiunto la fine dello 0 Il ciclo, i contiene il valore 6 ed esegue clog. Ciò spiega che sei registrato sei volte.