2

I am trying to use anonymous function for adding handler to anchor objects.

I run this code but it doesn't work can you explain why and try to fix it? Thanks:

var obj = document.getElementsByTagName("a");
var items = ["mouseover", "mouseout"];
for (var i = 0; i < items.length; i++) {
    (function() {
        var item = items[i];
        for (var i = 0; i < obj.length; i++) {
            obj[i]["on" + item] = function() {
                alert("thanks for your " + item);
            };      
        }
    })();               
}
antonjs
  • 14,060
  • 14
  • 65
  • 91
  • 2
    Can you include your expecting results and what did not work? – ajreal Sep 10 '11 at 10:58
  • 2
    Why do you even use an anonymous function? This makes no sense at all in this case. Just remove it. – Tomalak Sep 10 '11 at 10:58
  • 1
    *it doesn't work* is not a useful problem description. Anyway, there are several issues: Nested loops using same the same loop variable and creating closures in a loop without creating a new scope. – Felix Kling Sep 10 '11 at 10:59
  • @Tomalak: It does make sense, because otherwise you'd be overwriting `i`. – pimvdb Sep 10 '11 at 11:00
  • 2
    @pim There are other letters in the alphabet, not every loop counter must be named `i`. The function still makes no sense. – Tomalak Sep 10 '11 at 11:02
  • 2
    I wouldn't use "onX" assignment, I would use event registration like this - http://stackoverflow.com/questions/3763080/javascript-add-events-cross-browser-function-implementation-use-attachevent-adde. Or better still, use a cross-browser framework that hides this detail from you. – Paul Grime Sep 10 '11 at 11:03
  • 1
    @Tomalak: The anonymous function is used to create a closure that contains the `item` variable. – Guffa Sep 10 '11 at 11:20

2 Answers2

7

i is "hoisted"; in other words, your function is being executed as:

var i;
var item; // vars are hoisted to the beginning

item = items[i];
for (i = 0; i < obj.length; i++) {
    obj[i]["on" + item] = function() {
       alert("thanks for your " + item);
    };      
}

So i in items[i] is not referring to the outer i. Instead, i is undefined. You should use separate variable names, e.g. j for the inner loop:

for (j = 0; j < obj.length; j++) {

Second, item will change so the alert will use the same value each time. To circumvent this, you can use an anonymous function, but the point in them is to pass the value so that it "freezes":

var obj = document.getElementsByTagName("a");
var items = ["mouseover", "mouseout"];
for (var i = 0; i < items.length; i++) {
    var item = items[i];

    (function(item) {
        for (var j = 0; j < obj.length; j++) {
            obj[j]["on" + item] = function() {
                alert("thanks for your " + item);
            };      
        }
    })(item);               
}
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 3
    @Felix Kling: If you use `j` for the inner loop, `i` will have the correct value because it is not hoisted, so you can capture that safely. – pimvdb Sep 10 '11 at 11:05
  • 3
    Ah true.... then you still have to "capture" `item` ;) (otherwise the `alert` will output the same for all handlers (at least the ones created per iteration of the outer loop)). – Felix Kling Sep 10 '11 at 11:06
  • You are right again.... sorry, all these loops confuse me ;) I would probably just write `obj[j].onmouseover = obj[j].onmouseout = function(){...}` to avoid the inner loop... guess it's time to make a break now :) – Felix Kling Sep 10 '11 at 11:13
  • @Felix Kling: In this particular case it might be more straight-forward, but I guess `items` is something dynamic in the OP's scenario. But he's solved his problem anyway :) – pimvdb Sep 10 '11 at 11:16
5

The reason is that you are using a variable before it has been assigned a value:

var item = items[i];

The variable i is the local variable that is declared on the next line, not the variable from the surronding scope. Variables have function scope, so eventhough the variable is declared on the next line, it exist in the whole function.

You would need to use a different name for the variable in the function, if you want to be able to access the variable from the surrounding scope. For historic reasons, loop variables are usually named i, j, k as needed.

var obj = document.getElementsByTagName("a");
var items = ["mouseover", "mouseout"];
for (var i = 0; i < items.length; i++) {
  (function() {
    var item = items[i];
    for (var j = 0; j < obj.length; j++) {
      obj[j]["on" + item] = function() {
        alert("thanks for your " + item);
      };
    }
  })();               
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005