Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 263
pythonBOI Avatar asked May 10 '10 14:05

pythonBOI


2 Answers

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.

like image 186
Daniel Vassallo Avatar answered Nov 19 '22 22:11

Daniel Vassallo


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.

like image 37
Andy E Avatar answered Nov 19 '22 22:11

Andy E