0

This question is taken from the first question in the accepted answer here: https://stackoverflow.com/questions/1684917/what-questions-should-a-javascript-programmer-be-able-to-answer

<a href="#">text</a><br><a href="#">link</a>
 <script>
 var as = document.getElementsByTagName('a');
 for ( var i = as.length; i--; ) {
    as[i].onclick = function() {
        alert(i);
        return false;
    }
 }
</script>

Why do the anchors, when clicked on, alert -1 instead of their respective counter inside of the loop? How can you fix the code so that it does alert the right number? (Hint: closures)

Community
  • 1
  • 1
CodeToad
  • 4,656
  • 6
  • 41
  • 53

2 Answers2

4

When the onclick fires the value of i is -1 (because that's the last value i will get).

The solution is to make a closure that binds that value of i in that step of the iteration to the onclick-listener.

One possible way to fix it is:

for ( var i = as.length; i--; ) {
    as[i].onclick = (function(actually_i) {
        return function () {
            alert(actually_i);
            return false;
        }
    }(i)) // pass in i
}

There are other things wrong with this code. Here is how I would write it:

for (var i; i < as.length; i += 1) {
    as[i].addEventListener("click", (function(actually_i) {
        return function (event) {
            event.preventDefault();
            alert(actually_i);
        }
    }(i)) // pass in i
}
  • i-- construction is too clever and leads to bugs
  • better to use addEventListener instead of assigning onclick (modularity issues).
  • preventDefault is intended, not stopPropagation. return false does both and is always wrong.
Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • In summary, you use a closure to return the function that handles the click event. That function receives the current value of i inside the closure as input parameter. Thanks! – CodeToad Feb 10 '14 at 17:09
  • @CodeToad According to this definition : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures, the closure is the returned function, and it is so called only because it refers to an outer variable, as it happens, `actually_i`. –  Feb 10 '14 at 17:10
2

Current scenario :

  1. Loop 1 (i = 1) : sets click on first link.
  2. Loop 2 (i = 0) : sets click on second link.
  3. After the second loop, i equals -1.
  4. Click occurs : i still equals -1.

Using an IIFE allows to create an entirely new context in which the value of i has nothing to do with i anymore. Then, considering the example below, the term "closure" refers to the relationship between j and the returned function which will serve as a click handler :

var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
    as[i].onclick = function (j) {
        return function () {
            alert(i + ' ' + j);
            return false;
        };
    }(i);
}