4

In the documentation for the let statement in MDN, there's this example code:

var list = document.getElementById("list");

for (let i = 1; i <= 5; i++) {
  let item = document.createElement("li");
  item.appendChild(document.createTextNode("Item " + i));

  item.onclick = function (ev) {
    console.log("Item " + i + " is clicked.");
  };
  list.appendChild(item);
}

Then they state:

The example above works as intended because the five instances of the (anonymous) inner function refer to five different instances of the variable i.

I do not understand why there are "five different instances of the variable i.

The first statement in a for loop is always executed once, no? So the let statement is supposed to only execute once...
Once the code reaches the end of the iteration it checks the condition in the second statement.

How come, according to what they write, there's a new instance of i on each iteration?

Yuval A.
  • 5,849
  • 11
  • 51
  • 63
  • Not what you're asking, but I happened to test that behaviour last week and found that IE didn't implement it properly. (Chrome did.) – nnnnnn Jun 30 '16 at 09:09
  • 1
    In ECMA 6 specification, when the let expression is used, every iteration creates a new lexical scope chained up to the previous scope. http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation – Zoran Pandovski Jun 30 '16 at 09:12
  • To understand this, change that `let` to a `var` and see what happens when you click on the elements created by the loop – Jamiec Jun 30 '16 at 09:17
  • The first statement is executed only once, but I believe that the third statement (I++) equals to `let i = i + 1` and it is executed four times... is that correct? – Gerardo Furtado Jun 30 '16 at 09:18
  • @Jamiec, I understand *what* is happening, just not exactly *why*. – Yuval A. Jun 30 '16 at 09:22

2 Answers2

4

When the let expression is used, every iteration creates a new lexical scope chained up to the previous scope.

This means that every closure captures a different instance.

That is according ECMA 6 specification but not sure it'll work same way in all browsers.

Also not sure about the performance implications. I'd rather use this feature carefully.

Artur Udod
  • 4,465
  • 1
  • 29
  • 58
  • Before `let` IIFEs were used in order to achieve the same effect - with probably even worse performance. So what is your recommendation then? –  Jun 30 '16 at 09:19
  • @LUH3417 I'm just saying I'm `not sure` about the performance of such construct in different browsers. For example, have a look at this issue reported for chromium https://bugs.chromium.org/p/v8/issues/detail?id=4762 – Artur Udod Jun 30 '16 at 09:33
  • @LUH3417 and yes, probably iffy-s are even worse in terms of performance. So I'd better not recommend anything at all at this point – Artur Udod Jun 30 '16 at 09:34
1

In Javascript, variables are traditionally function-scoped. Blocks, such as for...loop statements, do not create a new scope.

If you declare a variable using var anywhere in your function (for instance, in a for loop), it will be declared only once at the top of the scope thanks to hoisting, and there will be only one instance of it in the entire function scope.

It can lead to issues when you invoke callbacks inside a for...loop.

// with hoisting, i is only declared once
for (var i in items) {  
    // the fn is called items.length times, before any callback is invoked
    _fetchItems(items[i], function() { 
        console.log("fetched for ", items{i]); 
        // for all callbacks, i is the same value items.length-1 
        // because they are called after the loop is complete
    });
}

Or in your example:

// with hoisting, i is only declared once
for (var i = 1; i <= 5; i++) {

    // with hoisting, item is only declared once
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Item " + i));

    // this function will be called after the for...loop is complete
    // so i value is unique: 5 + 1 = 6
    item.onclick = function (ev) {
        // => always return "Item 6 is clicked"
        console.log("Item " + i + " is clicked."); 
    };
    list.appendChild(item);
}

On the opposite, let variables are only scoped to the nearest block (ie. any code section between curly braces).

In your example, a new instance of the variable i is declared for each execution of the block inside the for...loop. igoes from 1 to 5, so there are 5 executions of the block, hence 5 instances of the variable.

They will each return the expected value "Item 1 is clicked.", "Item 2 is clicked.", etc.

paulwasit
  • 416
  • 2
  • 12
  • The point is that first statement in a `for` loop is always executed once. having it done as follows `for (var i = 1; i++; i < 5) { let t = i; ... }` would cause no questions. – Artur Udod Jul 01 '16 at 14:58