Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange things in JavaScript "for"

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?

like image 723
dmitq Avatar asked Jun 30 '10 14:06

dmitq


People also ask

What are the quirks of JavaScript?

The Quirks We'll look at:Dot Notation vs Bracket Notation. Function Context. Function Declarations vs Function Expressions. Named vs Anonymous Functions.

Is JavaScript weird?

JavaScript is a great programming language, but thanks to the fact that its initial release was built in only ten days back in 1995, coupled with the fact that JS is backward-compatible, it's also a bit weird.


2 Answers

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 (var 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 (var i = 1; i <= some_number; i++) {
  innerFunction(i);
}
like image 141
Hooray Im Helping Avatar answered Oct 19 '22 06:10

Hooray Im Helping


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:

  • Working with Closures
like image 39
Daniel Vassallo Avatar answered Oct 19 '22 08:10

Daniel Vassallo