I know this is probably "double-posted". But I am not able to assign the solutions to my problem.
I have 6 fileupload input fields. Whenever they change, I wanna alert "Changed!".
I want to iterate through thoes 6 fileupload id's with a for-loop.
Now, it gives me the error on variable i "Mutable variable is accessible from closure". I saw some solutions for this. But I'm not able to use these solutions for my problem.
function fileUploadCheck() {
for (var i = 1; i <= 6; i++) {
$("document").ready(function () {
$("#SOMEID"+i).change(function () {
alert('changed!');
});
});
}
}
jQuery uses implicit iteration. You don't have to loop manually.
$("input[type=file]").change(function(event) {
// check your console to see the value of `this`
console.log(this, "changed");
});
From the jQuery .each docs
Note: most jQuery methods that return a jQuery object also loop through the set of elements in the jQuery collection — a process known as implicit iteration. When this occurs, it is often unnecessary to explicitly iterate with the .each() method:
// The .each() method is unnecessary here:
$( "li" ).each(function() {
$( this ).addClass( "foo" );
});
// Instead, you should rely on implicit iteration:
$( "li" ).addClass( "bar" );
Regarding your "Mutable variable is accesible from closure", see this simplified example
for (var i=1; i<=6; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// 777777
// ALL SEVENS? WTF
The reason for that is, the closure depends on i, but i is changing outside of the closure. By the time any function is run, i has already set to 7, so the logged output for each function is 7.
If you use the method I have above, you won't have to worry about this at all. If you are still curious how you would fix this, please see
for (var i=1, fn; i<=6; i++) {
fn = (function(n) {
console.log(n);
})(i);
setTimeout(fn, 100);
}
// 123456
// YAY
Now each function is properly "bound" with an immutable i input; meaning the value of i will not change inside of the closure-wrapped function. Check out Function.prototype.bind if you're interested in shortcuts ^.^
Related
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);
I know that this code doesn't work and I also know why.
However, I do not know how to fix it:
JavaScript:
var $ = function(id) { return document.getElementById(id); };
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', function()
{
console.log(i);
});
}
});
HTML:
1
2
3
I want it to print the number of the link you clicked, not just "4".
I will prefer to avoid using the attributes of the node (id or content), but rather fix the loop.
Wrap the loop block in its own anonymous function:
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
(function(i) {
$('a' + i).addEventListener('click', function() {
console.log(i);
})
})(i);
}
}
This creates a new instance of i that's local to the inner function on each invocation/iteration. Without this local copy, each function passed to addEventListener (on each iteration) closes over a reference to the same variable, whose value is equal to 4 by the time any of those callbacks execute.
The problem is that the inner function is creating a closure over i. This means, essentially, that the function isn't just remembering the value of i when you set the handler, but rather the variable i itself; it's keeping a live reference to i.
You have to break the closure by passing i to a function, since that will cause a copy of i to be made.
A common way to do this is with an anonymous function that gets immediately executed.
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', (function(localI)
{
return function() { console.log(localI); };
})(i);
}
Since you're already using jQuery, I'll mention that jQuery provides a data function that can be used to simplify code like this:
for(var i = 1; i <= 3; i++)
{
$('a' + i).data("i", i).click(function()
{
console.log($(this).data("i"));
});
}
Here, instead of breaking the closure by passing i to an anonymous function, you're breaking it by passing i into jQuery's data function.
The closure captures a reference to the variable, not a copy, which is why they all result in the last value of the 'i'.
If you want to capture a copy then you will need to wrap it in yet another function.
I would like to assign the jQuery click-function for all elements in an array. But additionally, I need to access the array from within the click-function. The source will hopefully make it clearer:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event, mybox) {
event.preventDefault();
doStuffWithParameter(mybox);
});
}
// mybox is a JavaScript object (not jQuery-element!), myarray is an array, jqelem is a jQueryelement ala $("div.myclass");
The problem seems to be with function(event, mybox), apparently that doesn't work, i.e. mybox is unknown within the function. I think I 'kind of' understand why it cannot work this way, but how can this be achieved?
PS: I'm basically just doing it to save me from typing it manually for all array-elements.
Just remove the (useless) second callback function parameter named mybox.
If mybox is in scope in the outer block, it'll be in scope in the inner callback function too!
Should you need to know the appropriate value of i in the callback then you can do event registration-time binding:
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click({i: i}, function(event) {
// use "event.data.i" to access i's value
var my_i = event.data.i;
});
}
The map {i : i} corresponds with the eventData parameter in the jQuery .click() documentation.
When your click handler gets called, the first argument is the event data. jQuery doesn't pass in a second argument.
Update: Using closure to get to mybox object (notice I removed the 2nd argument)
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.click(function(event) {
event.preventDefault();
// here's the trick to get the correct i
(function(item) {
return function() {
doStuffWithParameter(mybox.myarray[item]);
};
})(i);
// you can access any object from parent scope here
// (except i, to get to right i, you need another trick),
// effectively creating a closure
// e.g. doOtherStuff(myarray)
});
}
Read more on closures here: http://jibbering.com/faq/notes/closures/
and here: How do JavaScript closures work?
You can take help of jquery data attributes
for (var i=0; i<mybox.myarray.length; i++) {
mybox.myarray[i].jqelem.data("arrayIndex", i).click(function(event) {
event.preventDefault();
doStuffWithParameter(mybox.myarray[$(this).data("arrayIndex")]);
});
}
I know this kind of question gets asked alot, but I still haven't been able to find a way to make this work correctly.
The code:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[i])}, false);
}
} // ends function
function toggle (element) {
alert (element);
}
The problem is in passing variables to the toggle function. It works with the this keyword (but that sends a reference to the clicked item, which in this case is useless), but not with elementsList[i] which alerts as undefined in Firefox.
As I understood it, using anonymous functions to call a function is enough to deal with closure problems, so what have I missed?
Try:
function startOfFunction() {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener(
"click",
(function(el){return function(){toggle(el);};})(elementsList[i]),
false
);
}
} // ends function
function toggle (element) {
alert (element);
}
The Problem is, that you want to use the var i! i is available in the onClick Event, (since closure and stuff). Since you have a loop, i is counted up. Now, if you click on any of the elements, i will always be elementsList.length (since all event functions access the same i )!
using the solution of Matt will work.
As an explanation: the anonymous function you use in the for loop references the variable "i" to get the element to toggle. As anonymous functions use the "live" value of the variable, when somebody clicks the element, "i" will always be elementsList.length+1.
The code example from Matt solves this by sticking the i into another function in which it is "fixated". This always holds true:
If you iterate over elements attaching events, do not use simple anonymous functions as they screw up, but rather create a new function for each element. The more readable version of Matts answer would be:
function iterate () {
for (var i = 0; i < list.length; i++) {
// In here, i changes, so list[i] changes all the time, too. Pass it on!
list[i].addEventListener(createEventFunction(list[i]);
}
}
function createEventFunction (item) {
// In here, item is fixed as it is passed as a function parameter.
return function (event) {
alert(item);
};
}
Try:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
(function(x) {
elementsList[x].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[x])}, false);
})(i);
}
} // ends function
I think it might be an issue with passing elementsList[i] around, so the above code has a closure which should help.
For some reason, when I try assigning a actionlisetner to the list elements the value does not stick. Here is what I mean:
Event.observe(window, 'load', function() {
for(i = 1; i <= $$('ul#review_list li').length; i++) {
$('cover_' + i).observe('click', function(event) {
alert(i);
});
}
});
So there are 7 list elements in #review_list and for some reason whenever any of the li elements are click I get an alert with value 8 for every element clicked. I want each to alert its respective i value. What am I doing wrong here?
Thanks!
Try this:
Event.observe(window, 'load', function() {
for(i = 1; i <= $$('ul#review_list li').length; i++) {
(function (i) { // i is passed as an argument below
$('cover_' + i).observe('click', function(event) {
alert(i); // creates a closure around the argument i
});
})(i); // pass i as an argument
}
});
The reason for which the first method does not work is because alert(i); creates a closure around the loop variable i, which gets incremented for each event assignment. At the time the first event will be fired, the value of i, which is common for all the events is 8, that's why you get 8 no matter where you click.
In the second method, the one I posted, alert(i) creates a closure around the argument i, which won't be shared with any other event listener.
Anyway, you should read this article on JavaScript closures to better understand them.
As Ionut G. Stan says, the issue is the closure over 'i'. RaYell is right about your wanting to declare the var (but not about that solving the problem).
That loop also repeatedly re-executes the $$ call, which isn't really ideal, and completely unnecessarily calls $ to look up elements you've already looked up (via $$).
FWIW:
Event.observe(window, 'load', function() {
$$('ul#review_list li').each(function(elm, index) {
++index; // Now it's 1-based
elm.observe('click', function(event) {
alert(index);
});
});
});
$$ looks up the elements, Enumerable#each then iterates through the result calling the given function with the element reference and its zero-based index in the array. The event handler is then a closure over several things, including the index parameter passed into the #each iterator.
Edit: I'm sorry, I just realized I made a massive assumption below: That the cover_x elements are, in fact, the list items under review_list. If they're not, disregard the below and my apologies! - T.J.
So that works, but it's also unnecessarily complex. Event delegation could be your friend here: Look for clicks on the list, rather than list items:
Event.observe(window, 'load', function() {
$('review_list').observe('click', function(event) {
var li;
li = event.findElement('li');
if (li) {
// ...your code here to interact with the list item.
// If you need the index, you can find it easily enough:
// index = parseInt(li.id.substring(6));
// ...since your IDs on the LI items is "cover_x" with x
// being the index.
}
});
});
Try adding a var keyword to your for loop. Without var you are assigning a global variable i, which is then incremented on every loop iteration. So after the loop it will have the value 8 and your alert is refering to that value.
Event.observe(window, 'load', function() {
for(var i = 1; i <= $$('ul#review_list li').length; i++) {
$('cover_' + i).observe('click', function(event) {
alert(i);
});
}
});