1

OK, I'm sure this is a super simple question but other answers I found were a bit too complex for me to understand. I want to create a series of buttons, each sending an alert of the loop iteration it was created in. However, every button sends an alert of "5", the value of i after the loop is finished. How do I make a copy of i instead of accessing the actual value? Is this passing by value or reference?

<span id="buttons"></span>
<script>    
  var buttons = document.getElementById("buttons");
  for (var i = 0; i < 5; i++) {
    var btn = document.createElement("button");
    btn.onclick = function(){alert(i);}
    btn.innerHTML = "Button " + i;
    buttons.appendChild(btn);
  }
</script>

JSFiddle

kreesh
  • 73
  • 1
  • 11
  • It doesn't make much sense because "id" values must be **unique**. That's why it's called an "id" — it gives the **identity** of the element. Use a class name and then use `.getElementsByClassName()` or `.querySelectorAll()` to access the elements. – Pointy Jan 29 '17 at 21:22

2 Answers2

2

This is because you have a closure around the i variable that is declared outside of the function that you are using it inside of.

Closures can be difficult to understand and you can learn more about them here.

Change the variable declaration to use let instead of var so that each loop iteration will get its own i scoped to it.

// let causes the variable to have block level scope and with a loop
// that means that a separately scoped variable is created for each
// loop iteration. This allows each iteration to have its own unique
// value for the variable that doesn't share a higher level scope.
for (let i = 0; i < 5; i++) {
        var btn = document.createElement("button");
        btn.onclick = function(){alert(i);}
        btn.innerHTML = "Button " + i;
        document.body.appendChild(btn);
}
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • It is important to point out the browser support of [let](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) - while some older browsers (e.g. IE 10-, certain versions of Safari, etc.) are not supported by many websites/web apps, some customers may need to support those browsers; Also see [caniuse](http://caniuse.com/#feat=let) for more information – Sᴀᴍ Onᴇᴌᴀ Jan 29 '17 at 21:53
2

One option is to wrap the event handler in a IIFE (immediately invoked function expression).

In doing so, you are capturing and passing the value of i on each iteration.

Updated Example

(function (i) {
  btn.onclick = function() {
    alert(i);
  }
})(i);

Another option would be to use the bind() method to pass the value of i on each iteration:

Updated Example

btn.onclick = function(index) {
  alert(index);
}.bind(null, i);

However, a better option may be to set a data-* attribute on the elements on each iteration, and then in the event listener you can access that element's data attribute:

Updated Example

btn.dataset.index = i;
btn.addEventListener('click', function() {
  alert(this.dataset.index);
});
Josh Crozier
  • 233,099
  • 56
  • 391
  • 304