I'm looking for something simple and straight forward, most of what I've pulled up on stack isn't quite what I need. I have an array that I want to loop through while calling a function after each iteration. What would that look like?
I'm assuming you're having problems with this because of the way closures are handled in Javascript. Douglas Crockford talks about this, in his book, by using the example of a function that assigns a click event handler to an array of nodes. The "intuitive" way is:
var addHandlers=function(nodes){
var i;
for(i=0; i<nodes.length;++i){
nodes[i].onClick= function {
alert (i);
};
}
};
However, this is not correct: each onClick callback will show the same value of i = nodes.length-1. This is because the value of i is not copied, but referenced in each inner function. The better way would be to create a helper function that returns a callback, something along the lines of the following:
var addHandlers = function (nodes) {
var helper = function (i){
return function (e){
alert (i);
}
}
for (int i =0; i<nodes.length();i++){
nodes [i].onClick=helper (i);
}
}
Plus, this allows you to avoid creating a function at each iteration.
var arr = [1,2,3];
for(var i = 0; i < arr.length; i++){
someFunction();
}
If you want to process one elment of the array to be used in an asynchronous funcion and then process the next next element you can do something like this;
function asynchCallback(arrayToProcess,someVar){
console.log("callback called with parameter:",someVar);
processArray(arrayToProcess);
}
function processArray(arr){
if(arr.length===0){
console.log("done");
return;
}
var someVar=tmp.splice(0,1);
setTimeout(function(){
asynchCallback(tmp,someVar[0]);
},100);
}
//send a copy of the array:
processArray([1,2,3].concat());
Related
This question already has an answer here:
Why is the loop assigning a reference of the last index element to? [duplicate]
(1 answer)
Closed 8 years ago.
I've created a simple observer model in a JavaScript WebApp to handle event-listeners on a more complex JS-Object model (no DOM events). One can register event listener functions that are then stored in an array. By calling a member function out of the wider application of the model the event listeners are executed. So far so good. Here's the implementation that works well:
var ModelObserver = function() {
this.locationObserverList = [];
}
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
fns(data); // function is being called
}
};
ModelObserver.prototype.registerLocationListener = function( fn) {
this.locationObserverList.push(fn);
};
If tested it with two listeners in a small sample html site, all good.
Now I want to make the call to the function asynchronously. I tried to change the code of the respective function as follows:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(function() {fns(data);}, 0);
}
};
Unfortunately I have a problem here: only the second listener is being called, but now twice. It seems to be a conflict with the fns variable, so I tried this:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
var fns = this.locationObserverList;
for(var i=0; i < this.locationObserverList.length; i++) {
setTimeout(function() {fns[i](data);}, 0);
}
};
Now I get an error: "Uncaught TypeError: Property '2' of object [object Array] is not a function".
Does anyone have an idea how to get this working asynchronously?
The anonymous function you're giving setTimeout has an enduring reference to the variables it closes over, not a copy of them as of when it was created.
You need to make it close over something else. Usually, you use a function that builds the function for setTimeout and closes over args to the builder:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(buildHandler(fns, data), 0);
// Or combining those two lines:
//setTimeout(buildHandler(this.locationObserverList[i], data), 0);
}
};
function buildHandler(func, arg) {
return function() {
func(arg);
};
}
There, we call buildHandler with a reference to the function and the argument we want it to receive, and buildHandler returns a function that, when called, will call that function with that argument. We pass that returned function into setTimeout.
You can also do this with ES5's Function#bind, if you're in an ES5 environment (or include an appropriate shim, as this is shimmable):
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(fns.bind(undefined, data), 0);
// Or combining those two lines:
//setTimeout(this.locationObserverList[i].bind(undefined, data), 0);
}
};
Skipping some details, that basically does what buildHandler above does.
More on this (on my blog): Closures are not complicated
Side note: By scheduling these functions to be called later via setTimeout, I don't think you can rely on them being called in order. That is, even if you schedule 1, 2, and 3, I don't know that you can rely on them being called that way. The (newish) spec for this refers to a "list" of timers, suggesting order, and so one might be tempted to think that registering timers in a particular order with the same timeout would have them execute in that order. But I don't (skimming) see anything in the spec guaranteeing that, so I wouldn't want to rely on it. A very quick and dirty test suggested the implementations I tried it on did that, but it's not something I'd rely on.
ModelObserver.prototype.emitEvent = function(eventtype, data) {
var fns = this.locationObserverList;
for(var i=0; i < this.locationObserverList.length; i++) {
(function(j){
setTimeout(function() {fns[i](data);}, 0);
}(i));
}
};
Try this
The second try is not going to work. In your first sample try -
setTimeout(function() {this.fns(data);}, 0);
JavaScript question. I'm completely drawing a blank this morning.
I have four functions:
function create_account1(){ // some stuff }
function create_account2(){ // some stuff }
function create_account3(){ // some stuff }
function create_account4(){ // some stuff }
I now have need to loop through some numbers and if a criteria is met, call the appropriate function. Since I don't know which number I'm on in the loop, how can I do this?
simple example:
for( var i=1; i<=4; i++ ){
create+i+();
}
That does not work and I've tried variations, but can get it working.
Thanks for any suggestions.
johnC
Do these function live in the global context?
If so, you could use this:
for( var i=1; i<=4; i++ ){
window["create_account" + i]();
}
FYI: If you want to test this in a jsFiddle, be sure to not use the onLoad or onDomready wrap settings of jsFiddle. If you try to use them, your functions will become nested ones which obviously don't belong to the window object anymore.
Try this
function mainfunc (){
window[Array.prototype.shift.call(arguments)].apply(null, arguments);
}
The first argument you pass should be a function name to call, all the rest of the arguments would be passed onto the called function. In your case,
mainfunc('create_account1');
or
mainfun('create_account2', 'Name');
if you need to pass a name for create_account2.
Please do by this:
for( var i=1; i<=4; i++ ){
var fnName = 'create_account' + i ;
//create+i+();
window[fnName]();
}
function create_account1(){
alert('Hello');
}
You cannot call function like this. I think you're facing design problem, the proper way to do it is something like this:
function create_account(accountID) {
switch(accountID)
{
case 1:
{
//account 1 logic.
break;
}
case 2:
{
//account 2 logic.
break;
}
etc...
}
}
And you call it like this
for( var i=1; i<=4; i++ ){
create_account(i);
}
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 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.
I have the following function. The problem is that instead of waiting for the user to click the image as expected, the function immediately fires the imgReplace function for each element in the images array.
Have I done something wrong?
Could the fact I'm using a separate Javascript routine based on Jquery be relevant here?
function setup () {
var images = document.getElementById("mycarousel");
images = images.getElementsByTagName("img");
for (var i = 0; i< images.length; i++) {
images[i].onclick = imgReplace (images[i]);
}
}
Wow I just fixed this embarrassing bug in some of my own code. Everybody else has gotten it wrong:
images[i].onclick = function() {imgReplace(images[i]);};
won't work. Instead, it should be:
images[i].onclick = (function(i) { return function() { imgReplace(images[i]); }; })(i);
Paul Alexander's answer is on the right track, but you can't fix the problem by introducing another local variable like that. JavaScript blocks (like the {} block in the "for" loop) don't create new scopes, which is a significant (and non-obvious) difference from Java or C++. Only functions create scope (setting aside some new ES5 features), so that's why another function is introduced above. The "i" variable from the loop is passed in as a parameter to an anonymous function. That function returns the actual event handler function, but now the "i" it references will be the distinct parameter of the outer function's scope. Each loop iteration will therefore create a new scope devoted to that single value of "i".
Your assigning the result of the call to imageReplace to the onclick handler. Instead wrap the call to imageReplace in it's own function
images[i].click = function(){ imgReplace( images[i] ) }
However, doing so will always replace the last image. You need to create a new variable to enclose the index
for (var i = 0; i< images.length; i++) {
var imageIndex = i;
images[i].onclick = function(){ imgReplace (images[imageIndex]); }
}
What you want to do here is:
images[i].onclick = function() {imgReplace(images[i]);}
try that.
Cheers