javascript var let

javascript - ¿Por qué los enlaces let y var se comportan de manera diferente usando la función setTimeout?



hoisting (2)

Con var tiene un alcance de función, y solo un enlace compartido para todas sus iteraciones de bucle, es decir, el i en cada devolución de llamada setTimeout significa la misma variable que finalmente es igual a 6 después de que finaliza la iteración del bucle.

Al let tener un alcance de bloque y cuando se usa en el bucle for , obtiene un nuevo enlace para cada iteración, es decir, el i en cada devolución de llamada setTimeout significa una variable diferente , cada una de las cuales tiene un valor diferente: el primero es 0, el el siguiente es 1 etc.

Así que esto:

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

es equivalente a esto usando solo var:

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

usando la expresión de función invocada inmediatamente para usar el alcance de la función de manera similar a como el alcance del bloque funciona en el ejemplo con let .

Podría escribirse más corto sin usar el nombre j , pero quizás no sería tan claro:

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

E incluso más corto con las funciones de flecha:

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

(Pero si puede usar las funciones de flecha, no hay razón para usar var .)

Así es como Babel.js traduce su ejemplo con let para ejecutar en entornos donde let no está disponible:

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

Gracias a Michael Geary por publicar el enlace a Babel.js en los comentarios. Vea el enlace en el comentario para una demostración en vivo donde puede cambiar cualquier cosa en el código y ver la traducción que se realiza de inmediato. Es interesante ver cómo se traducen también otras funciones de ES6.

Este código registra 6 , 6 veces:

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

Pero este código ...

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

... registra el siguiente resultado:

0 1 2 3 4 5

¿Por qué?

¿Es porque let une al alcance interno de cada elemento de manera diferente y var mantiene el último valor de i ?


Técnicamente es como @rsp explica en su excelente respuesta. Así es como me gusta entender que las cosas funcionan debajo del capó. Para el primer bloque de código usando var

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

Puedes imaginar que el compilador va así dentro 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

y así

como se declara usando var , cuando se llama a clog , el compilador encuentra la variable i en el bloque de función más cercano que es el timer y como ya hemos llegado al final del ciclo for , mantengo el valor 6 y ejecuto clog . Eso explica que 6 se registre seis veces.