Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 6 years ago.
Improve this question
I'm trying to convert a string into a function without using eval(), however whenever I pass the function name into the window object and check its type. Javascript does not seem to recognize it as a function. I always get this custom error message I've defined in the else statement: "Could not find function: 1->validateLogin".
My dom_ready prototype:
dom_ready: function (inputFunc) {
document.addEventListener("DOMContentLoaded", function () {
try {
inputFunc();
} catch (e) {
DN.errors.push(e.message);
}
});
},
function show_pass() {
...
}
function validateLogin(k) {
...
}
DN.DOM.dom_ready(function () {
var event_ids = [
["#login-form-dialog", "validateLogin", "submit", 1]
["#loginnotes", "validateLogin", "click", 1],
["#loginnotes2", "validateLogin", "click", 2],
["#show-pass", "show_pass", "click", ""],
]
for (var i = 0; i < event_ids.length - 1; i++) {
var fN = window[event_ids[i][1]];
if (typeof fN === 'function') {
$(event_ids[i][0]).on(event_ids[i][2], function () {
fN(event_ids[i][3]);
})
} else {
console.log("Could not find function: " + i + "->" + event_ids[i][1]);
}
}
});
The particular syntax error causing your problems was addressed in other answers. To find such syntax errors, look at the console for errors. Or, run your code through a linter. Otherwise, you will have to post to SO every time you forget a comma, which does not seem to be a very scalable approach.
More basically, do not pass around function references using strings giving their names, which you need to then look up on the window object. Instead, just pass the function reference itself (validateLogin). Unlike some other languages, in JS functions are first-class citizens which can be referred to and passed around as themselves. Your code would look like this:
DN.DOM.dom_ready(function () {
var event_ids = [
["#login-form-dialog", validateLogin, "submit", 1]
^^^^^^^^^^^^^
...
for (var i = 0; i < event_ids.length - 1; i++) {
var fN = event_ids[i][1];
Of course, you will have to make sure that validateLogin is visible at the time this ready function is executed.
However, you have a more basic problem which will prevent your code from running properly, in the following lines:
$(event_ids[i][0]).on(event_ids[i][2], function () {
fN(event_ids[i][3]);
})
Here, the anonymous function is a closure over the variable i, and at the time it is executed (when the event occurs), i will already be at its maximum value of 3. There are many questions and answers on this topic here on SO, but the easiest solution is to use for (let i, if you are working in an environment that supports let. Otherwise, see questions like this one.
You are missing a comma after the first item in event_ids:
var event_ids = [
["#login-form-dialog", "validateLogin", "submit", 1], // <-- missing comma
["#loginnotes", "validateLogin", "click", 1],
["#loginnotes2", "validateLogin", "click", 2],
["#show-pass", "show_pass", "click", ""],
]; // <-- also it is better practice to have semi-colon here
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a problem with calling a function with a parameter inside a setTimeout function. Basically I'm trying to make a small online game, where I create a queue of commands and then execute them one at a time (each takes some time to show a visualization).
Unfortunately it seems that I cannot pass any variable as a parameter inside the setTimeout(). Although the variable does exist when I call the function it does not exist later when it is executed. The function doesn't keep track of the passed value.
Is there any solution to this? Thanks a lot for any help. Here is a code I use:
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
var timeout = 0;
for (i = 0; i < commands.length; i++) {
console.log(commands[i].childNodes[0]); //variable exists
setTimeout(function() {go(commands[i].childNodes[0]);}, timeout+=400); //Uncaught TypeError: Cannot read property 'childNodes' of undefined
console.log(commands[i].childNodes[0]); //variable still exists
}
}
function go(command) {
//do somethig based on the passed command
}
When your functions are invoked, i is equal to commands.length and commands[i] is undefined.
They are capturing the variable i, not its value.
When they execute, they get out of i the actual value, but so far it has reached commands.length (that is the condition used to break your loop).
You can do something like this to work around it:
setTimeout(function(j) {
go(commands[j].childNodes[0]);
}.bind(null, i), timeout+=400);
Or this:
setTimeout((function(j) {
return function() {
go(commands[j].childNodes[0]);
};
})(i), timeout+=400);
Note also that, as you defined it, i is a global variable.
As mentioned in the comments by #PMV, there's a much easier way in modern JavaScript (if that's an option for you).
Just use a let statement as it follows:
for (let i = 0; i < commands.length; i++) {
// do whatever you want here with i
}
This will ensure that each iteration gets a new variable named i and you can capture it as in the original code.
You need to make a distinct copy of each item. By the time the setTimeout runs the loop has already finished.
var timeout = 0;
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
for (i = 0; i < commands.length; i++) {
go(commands[i]);
}
}
function go(command) {
setTimeout(function() {
console.log(command);
}, timeout += 400);
}
executeCommands();
<ul>
<li class="cmdplace">A</li>
<li class="cmdplace">B</li>
<li class="cmdplace">C</li>
<li class="cmdplace">D</li>
</ul>
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);
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
How do I get something from the for-loop in the event handler?
This json array
var elements = [ { "id": "#id1", "name": "text1" }, { "id": "#id2", "name": "text2" } ];
is passed to that function
function setHandlers(elements) {
for (var i = 0; i < elements.length; i++) {
$(document).on("focusout", elements[i].id, function() {
alert(elements[i].id); // doesn't work because 'element' isn't
// defined.
});
}
}
How can I access elements without defining it outside the function?
EDIT: types should be elements
Your code should look like that:
function setHandlers(elements) {
for (var i = 0; i < elements.length; i++) {
(function(i){
$(document).on("focusout", elements[i].id, function() {
alert(elements[i].id);
});
})(i);
}
}
You need an anonymous function to keep the i value in every loop. I.e. putting it in separate context fix the problem.
The problem is because of the closure variable i. You can use it by using a local closure
function setHandlers(elements) {
$.each(types, function(idx, obj){
$(document).on("focusout", obj.id, function() {
alert(obj.id); // doesn't work because 'element' isn't
// defined.
});
})
}
Note: You are iterating types, is it a mistake?
Here's an example of a situation where a simple JS loop does not behave as expected, because of the loop variable not being in a separate scope.
The solution often presented is to construct an unpleasant-looking bit of loop code that looks like this:
for (var i in obj) {
(function() {
... obj[i] ...
// this new shadowed i here is now no longer getting changed by for loop
})(i);
}
My question is, could this be improved upon? Could I use this:
Object.prototype.each = function (f) {
for (var i in this) {
f(i,this[i]);
}
};
// leading to this somewhat more straightforward invocation
obj.each(
function(i,v) {
... v ...
// alternatively, v is identical to
... obj[i] ...
}
);
when I ascertain that I need a "scoped loop"? It is somewhat cleaner looking and should have similar performance to the regular for-loop (since it uses it the same way).
Update: It seems that doing things with Object.prototype is a huge no-no because it breaks pretty much everything.
Here is a less intrusive implementation:
function each (obj,f) {
for (var i in obj) {
f(i,obj[i]);
}
}
The invocation changes very slightly to
each(obj,
function(i,v) {
... v ...
}
);
So I guess I've answered my own question, if jQuery does it this way, can't really go wrong. Any issues I've overlooked though would warrant an answer.
Your answer pretty much covers it, but I think a change in your original loop is worth noting as it makes it reasonable to use a normal for loop when the each() function isn't handy, for whatever reason.
Update: Changed to use an example that's similar to the example referenced by the question to compare the different approaches. The example had to be adjusted because the each() function requires a populated array to iterate over.
Assuming the following setup:
var vals = ['a', 'b', 'c', 'd'],
max = vals.length,
closures = [],
i;
Using the example from the question, the original loop ends up creating 2n functions (where n is the number of iterations) because two functions are created during each iteration:
for (i = 0; i < max; i++) {
closures[i] = (function(idx, val) { // 1st - factoryFn - captures the values as arguments
return function() { // 2nd - alertFn - uses the arguments instead
alert(idx + ' -> ' + val); // of the variables
};
})(i, vals[i]);
}
This can be reduced to creating only n + 1 functions by creating the factory function once, before the loop is started, and then reusing it:
var factoryFn = function(idx, val) {
return function() {
alert(idx + ' -> ' + val);
};
};
for (i = 0; i < max; i++) {
closures[i] = factoryFn(i, vals[i]);
}
This is nearly equivalent to how the each() function might be used in this situation, which would also result in a total of n + 1 functions created. The factory function is created once and passed immediately as an argument to each().
each(vals, function(idx, val) {
closures[idx] = function() {
alert(idx + ' -> ' + val);
};
});
FWIW, I think a benefit to using each() is the code is a bit shorter and creating the factory function right as it's passed into the each() function clearly illustrates this is its only use. A benefit of the for loop version, IMO, is the code that does the loop is right there so it's nature and behavior is completely transparent while the each() function might be defined in a different file, written by someone else, etc.
Global Scope
When something is global means that it is accessible from anywhere in your code. Take this for example:
var monkey = "Gorilla";
function greetVisitor () {
return alert("Hello dear blog reader!");
}
If that code was being run in a web browser, the function scope would be window, thus making it
available to everything running in that web browser window.
Local Scope
As opposed to the global scope, the local scope is when something is just defined and accessible in a
certain part of the code, like a function. For instance;
function talkDirty () {
var saying = "Oh, you little VB lover, you";
return alert(saying);
}
alert(saying); // Throws an error
If you take a look at the code above, the variable saying is only available within the talkDirty
function. Outside of it it isn’t defined at all. Note of caution: if you were to declare saying without
the var keyword preceding it, it would automatically become a global variable.
What this also means is that if you have nested functions, the inner function will have access to the
containing functions variables and functions:
function saveName (firstName) {
function capitalizeName () {
return firstName.toUpperCase();
}
var capitalized = capitalizeName();
return capitalized;
}
alert(saveName("Robert")); // Returns "ROBERT"
As you just saw, the inner function capitalizeName didn’t need any parameter sent in, but had complete
access to the parameter firstName in the outer saveName function. For clarity, let’s take another
example:
function siblings () {
var siblings = ["John", "Liza", "Peter"];
function siblingCount () {
var siblingsLength = siblings.length;
return siblingsLength;
}
function joinSiblingNames () {
return "I have " + siblingCount() + " siblings:\n\n" + siblings.join("\n");
}
return joinSiblingNames();
}
alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"
As you just saw, both inner functions have access to the siblings array in the containing function, and
each inner function have access to the other inner functions on the same level (in this case,
joinSiblingNames can access siblingCount). However, the variable siblingsLength in the siblingCount is
only available within that function, i.e. that scope.
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);