JavaScript for loop index strangeness [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm relatively new to JS so this may be a common problem, but I noticed something strange when dealing with for loops and the onclick function. I was able to replicate the problem with this code:
<html>
<head>
<script type="text/javascript">
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
buttons[i].onclick = function () {
alert(i);
return false;
}
}
}
</script>
</head>
<body>
hi
<br />
bye
</body>
</html>
When clicking the links I would expect to get '0' and '1', but instead I get '2' for both of them. Why is this?
BTW, I managed to solve my particular problem by using the 'this' keyword, but I'm still curious as to what is behind this behavior.

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 onclick callback is executed, the loop has 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);
return false;
};
}
var i;
for (i = 0; i < 2; i++) {
buttons[i].onclick = 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
Note: I would also suggest not to use var inside the for loop, because this may trick you in believing that the i variable has block scope, when on the other hand the i variable is just like the buttons variable, scoped within the function.

You need to store the state of the i variable, because by the time the event fires, the scoped state of i has increased to the maximum loop count.
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
(function (i) {
buttons[i].onclick = function () {
alert(i);
return false;
}
})(i);
}
}
The above example creates an anonymous function with a single argument i, which is then called with i being passed as that argument. This creates a new variable in a separate scope, saving the value as it was at the time of that particular iteration.

It's a order of execution issue
How to assign event callbacks iterating an array in javascript (jQuery)
Basically, the click handler accesses i well after the loop has exited, and therefore i is equal to 2.

Related

Closure inside a for Loop in Javascript with confusing results [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm a beginner in JavaScript, I've read every answer in similar topics, I still can't understand what's really going on because no one explained the part that I'm confused about.
I have a HTML document with two paragraphs, and this is what I'm using to change the color of a paragraph to red when I click on it :
var func = function() {
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
}
func();
The result is, where ever I click, only the last paragraph changes color to red. All the answers to questions similar to mine said that after the For loop is finished, the value of i will be 1, so that's what the closure will use, and then the eventListener will be added to same second paragraph ? And that I can fix this using a Immediately-Invoked Function or let to make i private to the closure, I don't know why should I make ì private if the closure has access to it at each iteration of the loop..
I just can't understand what's really going on here, isn't the loop executed line after another? At the start i will have a value of 0, so at Line 4 the variable p will have the first paragraph, then at Line 5-6 the function will use that p and attach the listener to it, then the loop gets executed a second time and i will have a value of 1, then at Line 5-6 again the closure gets the new value of p ?
I know the closure have access to the global variables here like i, so it have access to the p at Line 4 when its value changes.
What am I missing here please? Thank you very much in advance!
You are showing the proverbial closure example....
This can be difficult to grasp at first
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
Lines 5, 6 and 7 contain the anonymous callback function that is being stored in each paragraph. That function relies on the variable i from the parent function because the inner function uses p which is defined as paragraphs[i]. So, even though you aren't using i explicitly in the inner function, your p variable is. Let's say there are 7 paragraphs in the document... Because of this, you have 7 functions that are "closed" around the one i variable. i cannot go out of scope when the parent function terminates because the 7 functions need it. So, by the time a human comes along to click on one of the paragraphs, the loop has completed (i will now be 8) and each function is looking at the same i value.
To solve the problem, the click callback functions need to each get their own value, rather than share one. This can be accomplished in several ways, but they all involve PASSING a copy of i into the click callback function so that a COPY of the i value will be store in each of the click callback functions or removing the use of i alltogether. There will still be a closure, but there won't be the side effects you initially encountered because the nested functions won't be relying on variables from a parent function.
Here's an example that removes i from the nested function, thus solving the problem:
var paragraphs = document.querySelectorAll('p')
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].addEventListener('click', function() {
this.classList.toggle('red')
});
}
.red {color:red;}
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p class="red">Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
A closure is a function that closes in the values of the variables, so they don't change on the next iteration of the loop, which happens before the click happens etc.
var paragraphs = document.querySelectorAll('p');
for (var i = 0; i < paragraphs.length; i++) {
(function(p) { // <- any function call, would create a new scope, and hence a closure
p.addEventListener('click', function() {
p.classList.toggle('red'); // note that "this" would work here, instead of "p"
});
})(paragraphs[i]); // <- in this case, it's an IIFE, but it doesn't have to be
}
That would be a closure
In JavaScript (ECMA-Script 5 and earlier versions), only functions can create scopes.
In the other hand, closures don't capture variable values. That is, you need to do it yourself as you've already said using IIFEs (immediately-invoked function expressions):
for (var i = 0; i < paragraphs.length; i++) {
(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red')
})
})(paragraphs[i]);
}
BTW who knows if you can simplify this code using document.querySelectorAll:
// Now you don't need IIFEs anymore...
// Change the "p" selector with whatever CSS selector
// that might fit better in your scenario...
Array.from(document.querySelectorAll("p"))
.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
Well, in fact, you can refactor your code to use Array.prototype.forEach and you won't need to use IIFEs too:
paragraphs.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
This is the classic problem caused by your variable p being bound to a variable whose value has changed by the time the callback is invoked.
For iterating over an array and calling async code without any special tricks you can simply use Array.prototype.forEach (and on some browsers the classList property also supports this method):
paragraphs.forEach(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red');
});
});
Since p is a bound parameter of the forEach callback it always holds the expected value for the current iteration, and the correct element with toggle.
If your browser doesn't support classList.forEach then use:
[].prototype.forEach.call(paragraphs, ...);

Javascript hover event and local variable [duplicate]

Closures in a loop are causing me problems. I think I have to make another function that returns a function to solve the problem, but I can't get it to work with my jQuery code.
Here is the basic problem in a simplified form:
function foo(val) {
alert(val);
}
for (var i = 0; i < 3; i++) {
$('#button'+i).click(function(){
foo(i);
});
}
Naturally clicking on any of the three buttons will give an alert saying 3. The functionality I want is that clicking on button 1 will give an alert saying 1, button 2 will say 2 etc.
How can I make it do that?
See the bind method.
$('#button'+i).bind('click', {button: i}, function(event) {
foo(event.data.button);
});
From the docs:
The optional eventData parameter is
not commonly used. When provided, this
argument allows us to pass additional
information to the handler. One handy
use of this parameter is to work
around issues caused by closures
Try this code:
function foo(val) {
alert(val);
}
var funMaker = function(k) {
return function() {
foo(k);
};
};
for (var i = 0; i < 3; i++) {
$('#button'+i).click(funMaker(i));
}
Some important points here:
JavaScript is function scoped. If you want a new ('deeper') scope, you need to create a function to hold it.
This solution is Javascript specific, it works with or without jQuery.
The solution works because each value of i is copied in a new scope as k, and the function returned from funMaker closes around k (which doesn't change in the loop), not around i (which does).
Your code doesn't work because the function that you pass to click doesn't 'own' the i, it closes over the i of its creator, and that i changes in the loop.
The example could have been written with funMaker inlined, but I usually use such helper functions to make things clearer.
The argument of funMaker is k, but that makes no difference, it could have been i without any problems, since it exists in the scope of the function funMaker.
One of the clearest explanation of the 'Environment' evaluation model is found in 'Structure and Interpretation of Computer Programs', by Sussman & Abelson (http://mitpress.mit.edu/sicp/ full text available online, not an easy read) - see section 3.2. Since JavaScript is really Scheme with C syntax, that explanation is OK.
EDIT: Fixed some punctuation.
#Andy solution is the nicest. But you can also use Javascript scoping to help you save the value in your closure.
You do so by creating a new scope in your loop body by executing an anonymous function.
for (var i = 0; i < 3; i++) {
(function(){
var index = i;
$('#button'+index).click(function(){
foo(index);
});
})();
}
Since the loop body is a new scope at each iteration, the index variable is duplicated with the correct value at each iteration.
Use the .each function from jquery - I guess you a looping through similar elements - so add the click using something like:
$(element).children(class).each(function(i){
$(this).click(function(){
foo(i);
});
});
Not tested but I always use this kind structure where possible.
Or just manufacture a new function, as you describe. It would look like this:
function foo(val) {
return function() {
alert(val);
}
}
for (var i = 0; i < 3; i++) {
$('#button'+i).click(foo(i));
}
I'm pretty sure Mehrdad's solution doesn't work. When you see people copying to a temporary variable, it's usually to save the value of "this" which may be different within an inner child scope.

why javascript use call by reference , sometimes :D [duplicate]

This code is supposed to pop up an alert with the number of the image when you click it:
for(var i=0; i<10; i++) {
$("#img" + i).click(
function () { alert(i); }
);
}
You can see it not working at http://jsfiddle.net/upFaJ/. I know that this is because all of the click-handler closures are referring to the same object i, so every single handler pops up "10" when it's triggered.
However, when I do this, it works fine:
for(var i=0; i<10; i++) {
(function (i2) {
$("#img" + i2).click(
function () { alert(i2); }
);
})(i);
}
You can see it working at http://jsfiddle.net/v4sSD/.
Why does it work? There's still only one i object in memory, right? Objects are always passed by reference, not copied, so the self-executing function call should make no difference. The output of the two code snippets should be identical. So why is the i object being copied 10 times? Why does it work?
I think it's interesting that this version doesn't work:
for(var i=0; i<10; i++) {
(function () {
$("#img" + i).click(
function () { alert(i); }
);
})();
}
It seems that the passing of the object as a function parameter makes all the difference.
EDIT: OK, so the previous example can be explained by primitives (i) being passed by value to the function call. But what about this example, which uses real objects?
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(toggler);
}
Not working: http://jsfiddle.net/Zpwku/
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
(function (t) {
t.click(function () { t.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(t);
})(toggler);
}
Working: http://jsfiddle.net/YLSn6/
Most of the answers are correct in that passing an object as a function parameter breaks a closure and thus allow us to assign things to functions from within a loop. But I'd like to point out why this is the case, and it's not just a special case for closures.
You see, the way javascript passes parameters to functions is a bit different form other languages. Firstly, it seems to have two ways of doing it depending on weather it's a primitive value or an object. For primitive values it seems to pass by value and for objects it seems to pass by reference.
How javascript passes function arguments
Actually, the real explanation of what javascript does explains both situations, as well as why it breaks closures, using just a single mechanism.
What javascript does is actually it passes parameters by copy of reference. That is to say, it creates another reference to the parameter and passes that new reference into the function.
Pass by value?
Assume that all variables in javascript are references. In other languages, when we say a variable is a reference, we expect it to behave like this:
var i = 1;
function increment (n) { n = n+1 };
increment(i); // we would expect i to be 2 if i is a reference
But in javascript, it's not the case:
console.log(i); // i is still 1
That's a classic pass by value isn't it?
Pass by reference?
But wait, for objects it's a different story:
var o = {a:1,b:2}
function foo (x) {
x.c = 3;
}
foo(o);
If parameters were passed by value we'd expect the o object to be unchanged but:
console.log(o); // outputs {a:1,b:2,c:3}
That's classic pass by reference there. So we have two behaviors depending on weather we're passing a primitive type or an object.
Wait, what?
But wait a second, check this out:
var o = {a:1,b:2,c:3}
function bar (x) {
x = {a:2,b:4,c:6}
}
bar(o);
Now see what happens:
console.log(o); // outputs {a:1,b:2,c:3}
What! That's not passing by reference! The values are unchanged!
Which is why I call it pass by copy of reference. If we think about it this way, everything makes sense. We don't need to think of primitives as having special behavior when passed into a function because objects behave the same way. If we try to modify the object the variable points to then it works like pass by reference but if we try to modify the reference itself then it works like pass by value.
This also explains why closures are broken by passing a variable as a function parameter. Because the function call will create another reference that is not bound by the closure like the original variable.
Epilogue: I lied
One more thing before we end this. I said before that this unifies the behavior of primitive types and objects. Actually no, primitive types are still different:
var i = 1;
function bat (n) { n.hello = 'world' };
bat(i);
console.log(i.hello); // undefined, i is unchanged
I give up. There's no making sense of this. It's just the way it is.
It's because you are calling a function, passing it a value.
for (var i = 0; i < 10; i++) {
alert(i);
}
You expect this to alert different values, right? Because you are passing the current value of i to alert.
function attachClick(val) {
$("#img" + val).click(
function () { alert(val); }
);
}
With this function, you'd expect it to alert whatever val was passed into it, right? That also works when calling it in a loop:
for (var i = 0; i < 10; i++) {
attachClick(i);
}
This:
for (var i = 0; i < 10; i++) {
(function (val) {
$("#img" + val).click(
function () { alert(val); }
);
})(i);
}
is just an inline declaration of the above. You are declaring an anonymous function with the same characteristics as attachClick above and you call it immediately. The act of passing a value through a function parameter breaks any references to the i variable.
upvoted deceze's answer, but thought I'd try a simpler explanation. The reason the closure works is that variables in javascript are function scoped. The closure creates a new scope, and by passing the value of i in as a parameter, you are defining a local variable i in the new scope. without the closure, all of the click handlers you define are in the same scope, using the same i. the reason that your last code snippet doesn't work is because there is no local i, so all click handlers are looking to the nearest parent context with i defined.
I think the other thing that might be confusing you is this comment
Objects are always passed by reference, not copied, so the self-executing function call should make no difference.
this is true for objects, but not primitive values (numbers, for example). This is why a new local i can be defined. To demonstrate, if you did something weird like wrapping the value of i in an array, the closure would not work, because arrays are passed by reference.
// doesn't work
for(var i=[0]; i[0]<10; i[0]++) {
(function (i2) {
$("#img" + i2[0]).click(
function () { alert(i2[0]); }
);
})(i);
}
In the first example, there is only one value of i and it's the one used in the for loop. This, all event handlers will show the value of i when the for loop ends, not the desired value.
In the second example, the value of i at the time the event handler is installed is copied to the i2 function argument and there is a separate copy of that for each invocation of the function and thus for each event handler.
So, this:
(function (i2) {
$("#img" + i2).click(
function () { alert(i2); }
);
})(i);
Creates a new variable i2 that has it's own value for each separate invocation of the function. Because of closures in javascript, each separate copy of i2 is preserved for each separate event handler - thus solving your problem.
In the third example, no new copy of i is made (they all refer to the same i from the for loop) so it works the same as the first example.
Code 1 and Code 3 didn't work because i is a variable and values are changed in each loop. At the end of loop 10 will be assigned to i.
For more clear, take a look at this example,
for(var i=0; i<10; i++) {
}
alert(i)
http://jsfiddle.net/muthkum/t4Ur5/
You can see I put a alert after the loop and it will show show alert box with value 10.
This is what happening to Code 1 and Code 3.
Run the next example:
for(var i=0; i<10; i++) {
$("#img" + i).click(
function () { alert(i); }
);
}
i++;
You'll see that now, 11 is being alerted.
Therefore, you need to avoid the reference to i, by sending it as a function parameter, by it's value. You have already found the solution.
One thing that the other answers didn't mention is why this example that I gave in the question doesn't work:
for(var i=0; i<5; i++) {
var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
$("#container").append(toggler);
}
Coming back to the question months later with a better understanding of JavaScript, the reason it doesn't work can be understood as follows:
The var toggler declaration is hoisted to the top of the function call. All references to toggler are to the same actual identifier.
The closure referenced in the anonymous function is the same (not a shallow copy) of the one containing toggler, which is being updated for each iteration of the loop.
#2 is quite surprising. This alerts "5" for example:
var o;
setTimeout(function () { o = {value: 5}; }, 100);
setTimeout(function () { alert(o.value) }, 1000);

Why is array component declared outside function not visible inside [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
So I have the following code which doesn't recognise the buttons[i] variable inside the onclick function. Of course if I replace it with 'this', it works fine. (By 'works', I mean it functions as intended and changes the class of the body of the page.) My question is, if the array called 'buttons' is visible inside the anonymous function, why is buttons[i] not visible? Or is it the i that is not visible? I know that 'this' would always be used in such a case but I am just confused as to why buttons[i] does not also work.
(function(){
var buttons = document.getElementsByTagName("button");
for (var i =0, len = buttons.length; i < len; i++)
{
buttons[i].onclick = function(){
debugger
var className = buttons[i].innerHTML.toLowerCase();
// console.log(className);
document.body.className = className;
}
}
}());
Because by the time it's clicked, i == buttons.length, and buttons[i] is therefore undefined. There are many ways to deal with this, including binding the handler to the the right object, or introducing a closure that contains the right reference. One of the most backward-compatible ways is this:
for (var i =0, len = buttons.length; i < len; i++) {
buttons[i].onclick = (function(button) {
return function(){
var className = button.innerHTML.toLowerCase();
document.body.className = className;
};
}(buttons[i]));
}
You're having a problem with closures here. Because the loop will have executed before someone fires the click event your variable i = buttons.length which in this case would be five as there are five buttons. So when you're referencing buttons[i] you are in fact referencing buttons[5] which doesn't exist.
This is to do with javascript closures. This article explains closures quite well
buttons[i].onclick property takes in a function reference as an argument (in your case, you've passed in an anonymous function). The function that you are passing in however, is not in the same scope as your for loop. That means that you have to explicitly pass down any variables that are required inside that function. The variable button is available because you've passed that in to your anon function as a parameter.

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?
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:
Working with Closures
Because in the moment you click them, i == 5.
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+")");
(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.
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.

Categories

Resources