7

Please consider snippet below-

for(let i = 1; i <= 5; i++) {
   setTimeout(function(){
       console.log(i);
   },100);
}

In this case, logs inside setTimeout will contain values of variable i as per each iteration of the for loop, i.e the logs will be as follow

1
2
3
4
5

For this, I have read explanations over the Internet like - let creates a variable declaration for each loop which is block level declaration. So basically it creates a scope within { }.

But I am bit confused regarding this statement. If let creates a variable declaration for each loop, won’t it will be always initialized to 1 as per the loop initialization statement let i=1? Also, variable i is declared, initialized and incremented outside the loop block i.e. curly braces in the for loop statement. So, in each iteration isn’t the same variable i is incremented and utilized? How exactly does let creates a variable declaration for each loop and has value of previous iteration?

Abhinandan Khilari
  • 947
  • 3
  • 11
  • 26
  • Block-scoped `let` in this case means that `i` will be `undefined` beyond that loop, whereas with `var` it will be whatever the last value of it was. – Marty Dec 04 '19 at 06:45

3 Answers3

17

General Explanation

When you use let in the for loop construct like you show, there is a new variable i created for each invocation of the loop that is scoped just to the block of the loop (not accessible outside the loop).

The first iteration of the loop gets its value from the for loop initializer (i = 1 in your example). The other new i variables that are created each loop iteration get their value from the i for the previous invocation of the loop, not from the i = 1 which is why they aren't all initialized to 1.

So, each time through the loop there is a new variable i that is separate from all the other ones and each new one is initialized with the value of the previous one and then processed by the i++ in the for loop declaration.

For your ES6 code of this:

for(let i = 1; i <= 5; i++) {
   setTimeout(function(){
       console.log(i);
   },100);
} 

In ES5, this would be an essentially equivalent structure. If you really study this, it can be very informative for what's actually happening in your above ES6 code:

(function() {
    for (var i = 1; i <= 5; i++) {
        i = (function(j) {
            setTimeout(function(){
                console.log(j);
            },100);
            return j;
        })(i);
    }
})();

It takes two IIFE (immediately invoked function expressions) to simulate this. The outer one isolates the var i so that it doesn't leak out of the for loop and the inner one provides a separate scope and separate variable for each invocation of the for loop. The return j and the i = (function(j) {...})(i) is to show how the next iteration of the loop is affected by a modification to the loop variable.

Hopefully, this illustrates how incredibly useful let is for for loop in ES6 and how much other code it replaces when you need/want this functionality.

Now for Your Specific Questions

For this, I have read explanations over the Internet like - let creates a variable declaration for each loop which is block level declaration. So basically it creates a scope within { }

let defines variables that have block scope (not function scope like var). And, the { and } that delineate the for loop do indeed define a scope.

Also, variable i is declared, initialized and incremented outside the loop block i.e. curly braces in the for loop statement.

Well, not quite. The for loop is an instruction for how to initialize the first i created for the first invocation of the loop. The fact that the let i = 1 appears outside the loop does look a bit confusing, but it's really just an instruction for what to when it creates the first i variable for the first invocation of the loop. That first i variable doesn't actually exist outside the loop scope.

So, in each iteration isn’t the same variable i is incremented and utilized?

No. When ES6 encounters a for loop with a let definition, it creates a new variable for each iteration of the loop.

How exactly does let creates a variable declaration for each loop and has value of previous iteration?

It isn't let doing this. It's the for loop logic in the ES6+ JS interpreter. This is a special behavior for a for loop that has an index initializer declared with let. So, it's a combination behavior of let and for, but the real logic is in the way the for loop is executed by the interpreter.

Special Case When Modifying the Loop Variable

There is also a special case for let in for loops. If you assign to the value of i in the loop, it will change that particular value of i and it will also affect the next iteration's value of i. This is a bit of a special case, but it allows you to still manipulate the value of i in the loop.

for(let i = 1; i <= 5; i++) {
   let j = i;
   setTimeout(function(){
       console.log(j);
   },100);
   if (i === 2) {
      i++;         // bump the loop increment to skip the 3 value
   }
}

This creates the output:

1
2
4
5

So, this skips the 3 iteration of the loop because when i === 2, we increment it to 3, then the for loop does its i++ iteration and bumps it up to 4, effectively skipping the 3 iteration.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks for this, but just one small thing I think is important to mention. You don't need specifically an IIFE to accomplish this, just use an inner function: https://ibb.co/bgpjnvh or even better clean it up a bit: https://ibb.co/8BT832C – Mar Dec 30 '21 at 19:26
  • What happens is since `var` is scoped to the function, when a new inner function is called every iteration, a new inner var is created in memory for the inner function which is why you would now see the correct number. – Mar Dec 30 '21 at 19:28
  • Whereas the var without an inner function(the initial example with no inner function), the callback within setTimeout always gets the REFERENCE to it's parent var i in memory, NOT THE VALUE. – Mar Dec 30 '21 at 19:29
  • And since the reference keeps getting updated, finally when setTimeout runs, the for loop is already complete and setTimeout spits out the reference, which at that point is the last number. – Mar Dec 30 '21 at 19:30
  • 1
    @Mar - First off, your examples are kind of out-of-date because you're using arrow functions and if you have arrow function, you may as well just use `let` and not have to do the extra functions. Second, you are just substituting named functions and then calling them instead of using an IIFE - that's the exact same concept just with more lines of code. And, none of this is necessary any more since we can just use `let`. – jfriend00 Dec 30 '21 at 21:53
  • Right, so that's exactly the point I'm making here. You can use an IIFE or this concept which is just another way to do the same thing and in my opinion using an inner function where you can call is cleaner and more modular--but to each their own :) – Mar Dec 30 '21 at 23:25
  • 1
    @Mar - Either works just fine. The IIFE was invented and is used for a reason as it doesn't declare a needless symbol and is more compact. But, with `let` and `async`, there are far, far fewer reasons to ever use an IIFE any more. In fact, I just solved someone else's problem today [here](https://stackoverflow.com/questions/70536958/loop-async-await-createwritestream-and-delay/70537486#70537486) by removing several misued IIFEs because they are usually not needed in modern Javascript (due to both `async` and `let` in that example). – jfriend00 Dec 30 '21 at 23:32
8

A for loop like this:

for (let i = 1; i <= 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

Is the same as doing this:

{
  let i = 1;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 2;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 3;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 4;
  setTimeout(function() {
    console.log(i);
  }, 100);
} 

{
  let i = 5;
  setTimeout(function() {
    console.log(i);
  }, 100);
}

The variable is declared and assigned inside the scope of the for loop five separate times, and each instance is completely separate from the others.

symlink
  • 11,984
  • 7
  • 29
  • 50
0

In javascript you can define a variable through either var or let. The variables which are defined as let does not refined.Lets consider an example code snippet

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript let</h2>

<p id="demo"></p>

<script>
var i = 3;
for (var i = 0; i < 10; i++) {
  // some statements
  document.getElementById("demo").innerHTML += i;
}
document.getElementById("demo").innerHTML += i;
</script>

</body>
</html>

In this code the output will be --> 012345678910 In contrast, the variable with let in the loop does not redeclare the variable outside the loop.

<!DOCTYPE html>
<html>
<body>

   <h2>JavaScript let</h2>

    <p id="demo"></p>

    <script>
let i = 5;
for (let i = 0; i < 10; i++) {
  // some statements
  document.getElementById("demo").innerHTML += i;
}
document.getElementById("demo").innerHTML += i;
</script>

</body>
</html>

Output of above code will be --> 01234567895 Here the variable outside the loop remains unchanged and the inner loop executed and after the loop finished, global variable remains with the same value 5. It basically separates the variable according to their scope.

FAHAD SIDDIQUI
  • 631
  • 4
  • 22