It's easiest to think of this in terms of a timeline.
i
is a variable name that points to a value. When you go through the loop (the first thing to happen), i is being constantly reset to a higher value (first 0, then 1 etc). Each time it iterates you bind a function to a button. That function is not called yet, and it references i
.
Later, when a button is clicked, the function executes and looks for the value of i
. Because the loop has completed at this point, i
will be equal to the number of buttons (in this case 5).
The timeline
So basically:
...
i
is set to n (the number of buttons)
i
is greater than the loop condition and the loop completes
The user clicks a button
The callback is fired and references i
which is currently set to n
the alert fires with n as its value.
Fixing the issue
Lots of ways to fix it. The easiest old (backwards compatible way) is to pass the current value to a function (so it is read right away), and then use that to return a new function which has saved the old value in a closure:
for (i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", createHandler(i));
}
function createHandler(val) {
return function() {
alert("You just clicked " + val);
}
}
the new (ES6) way that may not work in all browsers is to use let, which limits a variable to block scope.
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function () {
alert("You just clicked " + i);
});
}