In below snippet, the function passed to setTimeout forms a closure with the variable i which is present in the script scope. So the function contains a reference to variable i. The value of i is updated to 5 before i is logged to console. And the output is: 5 5 5 5 5 (with newlines)
script.js:
let i;
for (i = 0; i < 5; ++i) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
The above part is clear to me. But when I declare variable i inside for loop, then i is block scoped to the for loop.
for (let i = 0; i < 5; ++i) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
In this code snippet, I expect the output to be same as output of first snippet, because the closure contains reference to the variable i and the value at that reference should be updated to 5 before the value is logged to console. But the actual output is: 0 1 2 3 4 (with newlines)
The question is, why the code in second snippet behaves this way?
It it because:
i is created in memory for each iteration of for loop and previous copy is garbage collected? I'm not sure how memory management works for loops.i, the value of i is stored in closure? I don't think this is the case.Please help to clarify. Thanks!
Your question mentions a few implementation details. Yet, these implementation details don't matter when answering your question.
You are indeed right, that there is some specific behavior of the for-loop occurring. Let's look at the ECMAScript 2021 standard here:
When your for-loop with let is evaluated, these steps are performed first:
Steps 4, 9 and 10 are particularly interesting to us. Step 4 gathers all const and let variables declared in the for-loop.
Step 9 sets perIterationLets to the let variables (if there are let variables) or to the empty list otherwise. Step 10 then calls ForBodyEvaluation to actually run the loop:
Here, let's focus on 3.e and take a closer look at CreatePerIterationEnvironment:
Carefully following the invocation hierarchy, we note that perIterationBindings is the list perIterationLets of let variables we gathered before.
In lines e.i, e.ii, e.iii, we now create a new binding (think of this as the memory location where the variables lives) and copy the value of the variable with the same name from the previous iteration into it. It is a different new variable though, even if it has the same name!
Thus if a closure inside a loop body captures the current context, the loop variables will not change, because each loop body iteration has its own current context.