Let variable in a JavaScript for loop

Viewed 1059

I checked the questions and answers about the same problem but non of them answered my question, so please don't discard my question.

When "i" is declared as a let variable, for each iteration, JS creates a new binding of "i" , thus each function of the SetTimout has it's own "i" inside it's closure (in opposite to the situation where "i" is declared as a var variable where all the SetTimout functions share the same binding in their closure). The question is : If JS creates a new binding of "i" at each iteration of the for loop when using let, so how the incremented value is transmitted to the new binding of i ?

How the new binding of i is instantiated with the present value of i?

Do we really have two binding of i in different scopes with different values if yes what are those scopes?

Thanks in advance

function a() {

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

a();

2 Answers

If JS creates a new binding of "i" at each iteration of the for loop when using let, so how the incremented value is transmitted to the new binding of i ?

At the end of the iteration, the values get copied over from the previous environment record to the next one.

Do we really have two binding of i in different scopes with different values if yes what are those scopes?

I'd like to introduce the term environment record here, which is the term the specification uses to describe the "thing" which binds values. There are multiple environment records (one for each iteration), all of them have the same scope (the values can be accessed inside the loop's body), and possibly different lifetimes and values.

You'll find all this in ES262, 13.7.4.9 Runtime Semantics: CreatePerIterationEnvironment

Anything you declare within a block scope with let will only be available there. But it will be a single instance. In your example the argument you gave to your console.log() function was a simple value (and not an object), therefore it was passed "by value" at the time of the calling in the for loop. The values being 0, 1 and 2. If you look at my modified example of a similar loop you will notice that in my case each setTimeOut() call will refer to the same object o and the property o.i is accessed at the time of execution, when it will be 3 (the value of o.i after the loop has ended) for all tree calls.

for (let i,o={i:0}; o.i < 3; o.i++) { 
    i=o.i;
    setTimeout(function () { console.log(i,o.i) }, o.i * 1000);
}

In order to demonstrate the difference between passing by value and passing by reference I added the local variale i into the mix. This variable gets updated by the up-to-date (in the for-loop) value of the the object property o.i in the loop and is then passed by value to the console.log() function. In contrast to that the expression console.log(i,o.i) will be executed at a later time, when the setTimeOut() function fires. And by then the property i of the object o will be 3.

Related