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?
The Quirks We'll look at:Dot Notation vs Bracket Notation. Function Context. Function Declarations vs Function Expressions. Named vs Anonymous Functions.
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.
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);
}
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With