Shouldn't each time it is invoked inside setTimeout just the value of i be passed and not its reference?
Yes, and this is exactly what is happening. It is passing the value of i
by value to console.log(i)
. But, you're passing that value AFTER the entire for
loop has completed when i
has its terminal value because this is the order that things run:
- You declare
let i
.
- You start your
for
loop.
- The
for
loop runs to completion, each iteration starting an additional timer. The timer is non-blocking and asynchronous so it just starts the timer and execution of the loop continues.
- Then, sometime later (after the
for
loop is completely done) with the value of i
sitting on its terminal value, then the timers start firing which causes them to call their callback and then output console.log(i)
.
Note, that if you made the let i
be part of the for
loop declaration, you would get completely different results:
for (let i = 1; i <= 5; i++) {
// Because let i was inside the for loop declaration,
// there is a separate copy of i for each iteration of the for loop here
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
Because here there is special treatment for the let i
in the for
loop. Each iteration of the loop gets it own separate copy of i
and thus it still has the value it had when the loop iteration was run at the later time when the timer fires.
This is completely different than your original version where there is only one i
for the entire loop and its value is incremented on each iteration and thus the value i
had when each iteration of the loop ran is no longer available when the timer fires.