0

can someone explain to me why the first code always print 3 and the second code work well?

var buttons = document.getElementsByTagName('button');
    for (var i = 0; i < buttons.length; i++) {
      buttons[i].addEventListener('click', function() {
        console.log('You clicked button #' + i);
      });
    }

vs

var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
  let j = i;
  buttons[j].addEventListener('click', function() {
    console.log('You clicked button #' + j);
  });
}

here is my codepen

Nave Hazan
  • 351
  • 5
  • 15
  • 1
    Basically https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable – Ezz Aug 05 '17 at 21:58
  • Have a look at the closures section – Ezz Aug 05 '17 at 22:10
  • @ezz sorry, my previous comment was incorrect. OP would have needed an IIFE to achieve the same with `var`. – jcaron Aug 05 '17 at 22:12
  • *var* creates a closure, *let* doesn't. See [*JavaScript closure inside loops – simple practical example*](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example?rq=1) – RobG Aug 05 '17 at 22:50

1 Answers1

1

When you add your event listener, you are creating a function that references the i variable from the enclosing scope. That variable gets incremented in your loop, and when the function is executed, you get the value of the variable at the end of the loop.

By creating another variable inside the loop, you are making a copy of the value at the time the loop is executed.

rossipedia
  • 56,800
  • 10
  • 90
  • 93
jcaron
  • 17,302
  • 6
  • 32
  • 46
  • thanks..that help a lot..so basically can i do it with "var" also? – Nave Hazan Aug 05 '17 at 22:13
  • Yes and no. `var` is function-scoped, so if you just change the `let` to `var`, it won't work because you'll still be referencing a single variable with the same scope as the `i` variable (but with a different final value). You would need an IIFE: enclose the bloc inside the loop inside a function which you call right away. `let` avoids this added level given its scope. – jcaron Aug 05 '17 at 22:17
  • I don't understand you explanation. Are you saying that the event function is not executed a each pass of the for loop ? – Jason Krs Aug 05 '17 at 22:24
  • ok...now i understand why with forEach i dont have this problem cos it always the same scope!!!...thank you so much – Nave Hazan Aug 05 '17 at 22:26
  • @JasonKrs it's only executed once the event actually happens, not right away in the loop – jcaron Aug 05 '17 at 22:34
  • @NaveHazan when you use `forEach` you necessarily have an inner function which creates a new scope, which is not the case with a regular `for` loop. – jcaron Aug 05 '17 at 22:35
  • @jcaron...so why when i use forEach i can use the "index" inside the inner function and it still work? – Nave Hazan Aug 05 '17 at 23:01
  • Sorry if I bother...Still don't get it... if executed only when event happens, when is `for` loop running ? I guess `var buttons` triggers it in both cases (on document ready somehow). If that is true, both counter should be 3 no matter what... added to that listener function only executed once would show both 3 in both cases... (I still dont get it) I think I nonetheless understand https://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable – Jason Krs Aug 05 '17 at 23:27
  • @NaveHazan yes, because the index is a parameter to the function, and has local scope to that function. – jcaron Aug 06 '17 at 07:25
  • @JasonKrs when the loop is executed, the event listener is only defined and assigned to the event, it will only be executed once the event happens. The ˋi` will be 3 in both cases, but in the second instance, a copy that is local to the block is made (`j`) and that is what is used inside the event listener. – jcaron Aug 06 '17 at 07:27
  • @jcaron I understand that. But what does copying with `let j` has to do with printing 3 in once case and not in the other ? I mean, both for loops will assign the even to one button on the array `buttons` and the function of the event should only execute (as you said) when the event occurs **for that specific button of the array** right ? – Jason Krs Aug 06 '17 at 08:50
  • In one case, the event handler prints `i`, which, at the time the event handler is executed, contains 3 as the loop has finished executing. In the other case, it prints `j`, which is a copy of the value of `i` at that iteration of the loop. – jcaron Aug 06 '17 at 21:23