It's all about the scope of the variable. The let statement declares a variable that is confined to a block scope. What is a block scope? Here is an example.
let a = 10; { let a = 20; console.log(a); // 20 } console.log(a); // 10
As seen inside the block and outside of it, ES6 creates completely different variables. The variable created by the let statement is visible only in its scope and outside its scope it is not possible to access it.
Here's what happens if you try the same with the var statement.
var a = 10; { var a = 20; console.log(a); // 20 } console.log(a); // 20
As you can see, the var operator does not fully comply with the block visibility rules.
Can your example be simplified for clarity
for(let i = 0; i < 10; i++) setTimeout(() => console.log(i), 1000)
1) Can be presented as follows
for(let i = 0; i < 10; i++){ setTimeout( function(){console.log(i);}, 1000 ) }
2) And break in rows
{ let i = 0; setTimeout( function(){console.log(i);}, 1000 ) } { let i = 1; setTimeout( function(){console.log(i);}, 1000 ) } { let i = 2; setTimeout( function(){console.log(i);}, 1000 ) } /** И так 10 раз .... После 1000 миллисекунд запуститься наша тайемр-функция, и поскольку в каждом блоке благодаря оператору let создавалась свая переменная то в таймер-функиции будет та переменная которая была создана в том же блоке.**/
But with the var operator, everything is different.
{ var i = 0; setTimeout( function(){console.log(i);}, 1000 ) } { var i = 1; setTimeout( function(){console.log(i);}, 1000 ) } { var i = 2; setTimeout( function(){console.log(i);}, 1000 ) } /** И так 10 раз ... Поскольку во всех блоках будет одна и та же переменная то после того как через 1000 миллисекунд запущенная таймер-функция прочитает последнее значение переменной i - которое у нас будет 10.**/
Something like this is how things stand with javascript. You can read more about block scope here and here .