Explicación del alcance de `let` y bloque con bucles for
Entiendo que eso let
evita declaraciones duplicadas, lo cual es bueno.
let x;
let x; // error!
Las variables declaradas con let
también se pueden usar en cierres que se pueden esperar
let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms
Lo que me cuesta un poco entender es cómo let
se aplica a los bucles. Esto parece ser específico de for
los bucles. Considere el problema clásico:
// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
¿Por qué funciona el uso let
en este contexto? En mi imaginación, aunque solo es visible un bloque, for
en realidad crea un bloque separado para cada iteración y la let
declaración se realiza dentro de ese bloque... pero solo hay uno.let
declaración para inicializar el valor. ¿Es esto solo azúcar sintáctico para ES6? ¿Cómo funciona esto?
Entiendo las diferencias entre var
y let
y las he ilustrado arriba. Estoy particularmente interesado en comprender por qué las diferentes declaraciones dan como resultado resultados diferentes usando un for
bucle.
¿Es esto solo azúcar sintáctico para ES6?
No, es más que azúcar sintáctico. Los detalles sangrientos están enterrados en §13.6.3.9
CreatePerIterationEnvironment
.
¿Cómo funciona esto?
Si usa esa let
palabra clave en la for
declaración, verificará qué nombres vincula y luego
- cree un nuevo entorno léxico con esos nombres para a) la expresión inicializadora b) cada iteración (antes de evaluar la expresión incremental)
- copiar los valores de todas las variables con esos nombres de un entorno al siguiente
Su declaración de bucle for (var i = 0; i < 10; i++) process.nextTick(_ => console.log(i));
se reduce a un simple
// omitting braces when they don't introduce a block
var i;
i = 0;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
…
mientras que for (let i = 0; i < 10; i++) process.nextTick(_ => console.log(i));
hace "desazúcar" a lo mucho más complicado
// using braces to explicitly denote block scopes,
// using indentation for control flow
{ let i;
i = 0;
__status = {i};
}
{ let {i} = __status;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
…
Esta explicación del libro Explorando ES6 me pareció la mejor:
La declaración var de una variable en el encabezado de un bucle for crea un enlace único (espacio de almacenamiento) para esa variable:
const arr = []; for (var i=0; i < 3; i++) { arr.push(() => i); } arr.map(x => x()); // [3,3,3]
Cada i en los cuerpos de las tres funciones de flecha se refiere al mismo enlace, razón por la cual todas devuelven el mismo valor.
Si deja declarar una variable, se crea un nuevo enlace para cada iteración del bucle:
const arr = []; for (let i=0; i < 3; i++) { arr.push(() => i); } arr.map(x => x()); // [0,1,2]
Esta vez, cada i se refiere al enlace de una iteración específica y conserva el valor actual en ese momento. Por lo tanto, cada función de flecha devuelve un valor diferente.