views:

191

answers:

7

Hi. I'm using jQuery and I have a strange thing that I don't understand. I have some code:

for (i = 1; i <= some_number; i++) {
    $("#some_button" + i).click(function() {
        alert(i);
    });
}

"#some_button" as the name says - they are some buttons. When clicked they should pop-up a box with it's number, correct? But they don't. If there is 4 buttons, they always pop-up "5" (buttons count + 1). Why is that so?

+15  A: 

It has to do with JavaScript scoping. You can get around it easily by introducing another scope by adding a function and having that function call itself and pass in i:

for (i = 1; i <= some_number; i++) {
  (function(j) {
    $("#some_button" + j).click(function() {
      alert(j);
    });
  })(i);
}

This creates a closure - when the inner function has access to a scope that no longer exists when the function is called. See this article on the MDC for more information.

EDIT: RE: Self-calling functions: A self-calling function is a function that calls itself anonymously. You don't instantiate it nor do you assign it to a variable. It takes the following form (note the opening parens):

(function(args) {
  // function body that might modify args
})(args_to_pass_in);

Relating this to the question, the body of the anonymous function would be:

$("#some_button" + j).click(function() {
  alert(j);
});

Combining these together, we get the answer in the first code block. The anonymous self-calling function is expecting an argument called j. It looks for any element with an id of some_button with the integer value of j at the end (e.g. some_button1, some_button10). Any time one of these elements is clicked, it alerts the value of j. The second-to-last line of the solution passes in the value i, which is the loop counter where the anonymous self-calling function is called. Done another way, it might look like this:

var innerFunction = function(j) {
  $("#some_button" + j).click(function() {
    alert(j);
  });
};

for (i = 1; i <= some_number; i++) {
  innerFunction(i);
}
Hooray Im Helping
dear god :(Could you please, pretty please, rewrite it in a more readable manner? Nobody who doesn't already know the answer will ever understand a thing from the piece you wrote. `);});})(i);}` ...
SF.
@SF: this is pretty much what you gotta do.. Maybe this will help: you turn `for (...) { LOGIC(i); }`, where `LOGIC` is arbitrary code operating on the variable `i`, into `for (...) { (FUNC)(i) }`, immediately creating a function and giving it `i`. `FUNC` is simply `function (j) { LOGIC(j); }`.
Claudiu
A: 

See my answer here.

Peter Bailey
Why on earth does this deserve a downvote? The fact that this question is getting so many answers when it's already a duplicate of several others is dubious enough. It's not like the linked answer is wrong.
Peter Bailey
+7  A: 

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 click callback is called, the loop will have 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);
   };  
} 

var i;

for (i = 0; i < some_number; i++) {
    $("#some_button" + i).click(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:

Daniel Vassallo
A: 
  (function (some_number) {
    for (i = 1; i <= some_number; i++) {
      $("#some_button" + i).click(function() {
        alert(i);
      });
    }
  })(some_number);

Wrap the function outside because for speed and the fact i will keep resetting.

RobertPitt
Are you sure you are wrapping the right part ?
HoLyVieR
+1  A: 

Because in the moment you click them, i == 5.

Matteo Mosca
heh very succinct, but this will only help someone who already knows what the problem is
Claudiu
A: 

This is because of how closures work in JavaScript. Each of the 5 functions you are creating is basically sharing the same i variable. The value of i inside your function is not being evaluated when you are creating the function, but when the click event occurs, by which time the value of i is 5.

There are various techniques for getting around this (when this behavior isn't what you want). One (if you have a simple function, like you do here) is to use the Function constructor instead of a function literal:

$("#some_button" + i).click(new Function("alert("+i+")");
jhurshman
A: 

This is very clever code. So clever it's a question on SO. :) I'd sidestep the question altogether by dumbing the code down, just to have a chance at understanding it (or having a colleague understand it) six months from now. Closures have their place, but in this case I'd avoid them in favour of more understandable code.

Probably, I'd attach the same function to all the buttons, which would get the button from the event, strip "some_button" from the ID, and alert the result. Not nearly as pretty, but I guarantee everyone in the office could follow it at a glance.

Ed Daniel
Is it better to have complex code to work around a problem, or avoid the problem altogether?
Ed Daniel