JavaScript for loop index strangeness [duplicate]
I'm relatively new to JS so this may be a common problem, but I noticed something strange when dealing with for loops and the onclick function. I was able to replicate the problem with this code:
<html>
<head>
<script type="text/javascript">
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
buttons[i].onclick = function () {
alert(i);
return false;
}
}
}
</script>
</head>
<body>
<a href="">hi</a>
<br />
<a href="">bye</a>
</body>
</html>
When clicking the links I would expect to get '0' and '1', but instead I get '2' for both of them. Why is this?
BTW, I managed to solve my particular problem by using the 'this' keyword, but I'm still curious as to what is behind this behavior.
Solution 1:
You are having a very common closure problem in the for
loop.
Variables enclosed in a closure share the same single environment, so by the time the onclick
callback is executed, the loop has run its course and the i
variable will be left pointing to the last entry.
You can solve this with even more closures, using a function factory:
function makeOnClickCallback(i) {
return function() {
alert(i);
return false;
};
}
var i;
for (i = 0; i < 2; i++) {
buttons[i].onclick = makeOnClickCallback(i);
}
This can be quite a tricky topic, if you are not familiar with how closures work. You may to check out the following Mozilla article for a brief introduction:
- Working with Closures
Note: I would also suggest not to use var
inside the for
loop, because this may trick you in believing that the i
variable has block scope, when on the other hand the i
variable is just like the buttons
variable, scoped within the function.
Solution 2:
You need to store the state of the i
variable, because by the time the event fires, the scoped state of i
has increased to the maximum loop count.
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
(function (i) {
buttons[i].onclick = function () {
alert(i);
return false;
}
})(i);
}
}
The above example creates an anonymous function with a single argument i
, which is then called with i
being passed as that argument. This creates a new variable in a separate scope, saving the value as it was at the time of that particular iteration.