5

I understand that let has block scope and var has functional scope. But I do not understand in this case, how using let will solve the problem

const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
   console.log(arr[i]) 
}, 1000);
} // Prints undefined 5 times

const arr = [1,2,3,4];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
   console.log(arr[i]) 
}, 1000);
} // Prints all the values correctly
0

3 Answers 3

4

This is all related to the scope of the variable. Let's try to wrap both the pieces into functions, and observe the output:

function test() {
  // `i` will be declared here, making it a non-for-loop scoped variable
  const arr = [1, 2, 3, 4];
  for (var i = 0; i < arr.length; i++) {
    setTimeout(function() {
      console.log(arr[i])
    }, 1000);
  } // Prints undefined 5 times
}

test();

So in the first case, i will be hoisted, and because of the asynchronous nature of setTimeout, i will immediately become 4 as the loop ends without waiting. This will make arr[i] to point to an undefined element in the array.

In the second case, i is not hoisted, and has scoped access to each iteration of the loop, making i accurately available to console.log statement. Thus the results are as per the expectations:

function test() {
  const arr = [1, 2, 3, 4];
  for (let i = 0; i < arr.length; i++) {
    setTimeout(function() {
      console.log(arr[i])
    }, 1000);
  } // Prints all the values correctly

}

test();

Sign up to request clarification or add additional context in comments.

7 Comments

I still don't get it though. Is let scoped around for, or inside for? Is there four different i, or one? If one, how is it different from var situation (presumably the one i would still be captured in closure, just like with var)? If four, why does i++ work (i.e. how does both old i and new i get accessed)? If anyone has relevant passages of ES2018, that would be amazeballs. (EDIT: the proposed duplicate has the answer.)
Umm sorry I didn't get it. Can please you explain what do you mean by hoisted?
In the second case, i is not hoisted, and has scoped access to each iteration of the loop, making i accurately available to console.log statement. That is right but the execution of loop won't stop for setTimeout.In this case also the loop will finish and if you log the value of i outside settimeout, you will see it's value i is set to length-1 much before the settimeout have started execution. So the question is how settimeout get the value of i even when its value is set length-1
In the second example, the binding of i changes for each iteration, making an impression of having separate copies of i. Please go through the original post (for which this question was marked as duplicate) to clarify more. :)
@Amadan In ES6+ i is inside the block, and a new lexical environment is created for each iteration. Under "IterationStatement : for ( LexicalDeclaration Expression ; Expression ) Statement" in section 13.7.4.7 of the ECMA2017 edition, a loop environment is created in step 2 and set in place in step 7. Per section 13.7.4.8, the loop environment is copied before the first loop iteration (step 2) and copied again in its existing state at the end of the loop, before the `++i' increment (step 3.e) The previous environment is restored after the loop terminates.
|
1

First of all, the output will be four times and not five times(as mentioned in your comment). I pasted your code in Babel REPL and this is what I got,

"use strict";

var arr = [1, 2, 3, 4];

var _loop = function _loop(i) {
setTimeout(function () {
   console.log(arr[i]);
}, 1000);
};

for (var i = 0; i < arr.length; i++) {
_loop(i);
}

Do you see how let works internally now? :-)

Comments

0

You can still use var for setTimeout. You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout such that the value of i is recognised by the setTimeout function.

const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
(function(i){
setTimeout(function() {
   console.log(arr[i]) 
}, 1000)})(i);
}

2 Comments

That does not answer the question "why does let work".
Thanks for your answer Ankit. But I know how to make it work using IIFE. I am just wondering how 'let' makes this work.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.