Defining anonymous functions in a loop including the looping variable? - javascript

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.

Related

JavaScript Function inside the loop

Can someone explain to me why JSLint complains about "Function inside the loop" with this example:
for (var i = 0; i < buttons.length; i++) {
(function(i) {
buttons[i].onclick = function(e) {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
})(i);
}
But dosen't when I change it to:
function makeHandler(i)
{
return function() {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = makeHandler(i);
}
I don't quite understand as it seems that with each loop iteration new function object has to be returned, even though it happens inside of makeHandler() function. Why the second example is ok with JS linters?
Quoting from linterrors,
var elems = document.getElementsByClassName("myClass"), i;
for (i = 0; i < elems.length; i++) {
(function (iCopy) {
"use strict";
elems[i].addEventListener("click", function () {
this.innerHTML = iCopy;
});
}(i));
}
What we have now captures the value of i at each iteration of the loop. This happens because JavaScript passes arguments to functions by value. This means that iCopy within the capturing function is not related to i in any way (except for the fact that they happen to have the same value at that point in time). If i changes later (which it does - on the next iteration of the loop) then iCopy is not affected.
This will work as we expect it to but the problem now is that the JavaScript interpreter will create an instance of the capturing function per loop iteration. It has to do this because it doesn't know if the function object will be modified elsewhere. Since functions are standard JavaScript objects, they can have properties like any other object, which could be changed in the loop. Thus by creating the function in the loop context, you cause the interpreter to create multiple function instances, which can cause unexpected behavior and performance problems. To fix the issue, we need to move the function out of the loop:
I would have liked to use Array.prototype.forEach here, like this
buttons.forEach(function(curButton) {
curButton.onclick = function(e) {
t.progressBars[t.current].update(curButton.getAttribute("data-value"));
};
});
Your two examples are not equivalent.
In the first, you are creating an anonymous function and calling it on every loop.
The inner function (the click event handler) is fine - you're assigning a new function - but it's the anonymous outer function that is inefficient in this context. In your second example the outer function is refactored out of the loop where is it only created once, instead of buttons.length times.

Wrong Parameter is passed to function

I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}

Javascript,Setting up onclick method syntax

I am looking at a javascript code that manipulates an HTML A tag , and I'm having trouble understanding how it sets up the "onclick" property. It seems to be telling it to update ytplayer_playitem with the index variable j and then call ytplayer_playlazy(1000)
But what's up with all the parentheses? What details in the javascript syntax allows it to be setup like this?
var a = document.createElement("a");
a.href = "#ytplayer";
a.onclick = (function (j) {
return function () {
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
})(i);
Well, basically, the value of onclick is a function that will get called when the element is clicked. Whatever you want to happen when the user clicks the element goes in the body of the function.
You could create a named function and then assign it to the element's onclick attribute:
function youClickedMe() {
...
}
a.onclick = youClickedMe
but that clutters up the namespace with a function name that is never referenced anywhere else. It's cleaner to create an anonymous function right where you need it. Normally, that would look like this:
a.onclick = function() { ... }
But if we try that with your specific example:
a.onclick = function() {
ytplayer_playitem = something; // ??
ytplayer_playlazy(1000);
}
We see that it hard-codes the something that gets played. I'm assuming the original code was taken from a loop which generates several clickable links to play; with the code just above, all of those links would play the same thing, which is probably not what you want.
So far, so straightforward, but this next leap is where it gets tricky. The solution seems obvious: if you're in a loop, why not just use the loop variable inside the function body?
// THIS DOESN'T WORK
a.onclick = function() {
ytplayer_playitem = i;
ytplayer_playlazy(1000);
}
That looks like it should work, but unfortunately the i inside the function refers to the value of the variable i when the function is called, not when it's created. By the time the user clicks on the link, the loop that created all the links will be done and i will have its final value - probably either the last item in the list or one greater than that item's index, depending on how the loop is written. Whatever its value is, you once again have the situation where all links play the same item.
The solution in your code gets a little meta, by using a function whose return value is another function. If you pass the loop control variable to the generating function as an argument, the new function it creates can reference that parameter and always get the value that was originally passed in, no matter what has happened to the value of the outer argument variable since:
function generate_onclick(j) {
// no matter when the returned function is called, its "j" will be
// the value passed into this call of "generate_onclick"
return function() { ytplayer_playitem = j; ytplayer_playlazy(1000); }
}
To use that, call it inside the loop like this:
a.onclick = generate_onclick(i);
Each generated function gets its very own j variable, which keeps its value forever instead of changing when i does. So each link plays the right thing; mission accomplished!
That's exactly what your posted original code is doing, with one small difference: just like the first step in my explanation, the author chose to use an anonymous function instead of defining a named one. The other difference here is that they are also calling that anonymous function immediately after defining it. This code:
a.onclick = (function (j) { ... })(i)
is the anonymous version of this code:
function gen(j) { ... }
a.onclick = gen(i)
The extra parens around the anonymous version are needed because of JavaScript's semicolon-insertion rules; function (y) {...}(blah) compiles as a standalone function definition followed by a standalone expression in parentheses, rather than a function call.
"But what's up with all the parentheses? "
Most of the parentheses are just doing what you'd expect.
There's an extra set that isn't technically needed, but is often used as a hint that the function is being invoked.
// v-v---these are part of the function definition like normal
a.onclick = (function (j) {
// ^-----------this and...v
return function () {
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
// v---...this are technically not needed here, but are used as a hint
})(i);
// ^-^---these invoked the function like normal
"What details in the javascript syntax allows it to be setup like this?"
The upshot is that the function is invoked immediately, and passed i so that its value is referenced by the j parameter in the immediately invoked function.
This creates a variable scope that the returned function will continue to have access to. This way it always has access to the j variable, and not the i that gets overwritten in the loop.
These inlined functions are abused a bit IMO. It becomes clearer if you simply make it a named function.
for(var i = 0; i < 10; i++) {
// create the new element
a.onclick = createHandler(i);
// append it somewhere
}
function createHandler (j) {
return function () {
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
}
The resulting handler is exactly the same, but the code is much less cryptic.
Right, I'm going to guess that the surrounding code looks like this:
for (var i = 0; i < playitems.length; i++) {
// above code here
}
Now, you could do the obvious thing here, and assign the onclick property like this:
a.onclick = function() {
ytplayer_playitem = i;
ytplayer_playlazy(1000);
};
However that wouldn't work very well, because the value of i changes. Whichever link was clicked, the last one would be the one activated, because the value of i at that point would be the last one in the list.
So you need to prevent this happening. You need to do this by creating a new scope, which is done by creating an extra function, which is immediately invoked:
(function (j) {
// some code here
})(i);
Because i has been passed into the function, the value is passed rather than a reference to the variable being kept. This means that you can now define a function which will have a reference to the correct value. So you get your extra function to return the click handling function:
a.onclick = (function (j) { // j is the right number and always will be
return function () { // this function is the click handler
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
})(i);
So each a element has its own click handler function, each of which has its own individual j variable, which is the correct number. So the links, when clicked, will perform the function you want them to.
a.onclick = (function (j) {
return function () {
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
})(i);
This creates a "closure" to ensure that the value of i that is bound to the handler is the value of i "at that time" and not i in the general.
In your code, the function inside the () is an expression, executed and passed the variable i. This is the (i) you see in the end part. In this executed function expression, the i becomes the local variable j. This executed function expression returns the handler function that is to be bound the onclick event carrying the value of j which was i "at that time"
if i did not use the closure:
//suppose i is 1
var i = 1;
a.onclick = function () {
ytplayer_playitem = i;
ytplayer_playlazy(1000);
};
//and changed i
i = 2;
//if you clicked the <a>, it would not be 1 onclick but 2 because you
//did not assign the value of i "at that time". i is "tangible" this way
a.onclick = (function (j) {
return function () {
ytplayer_playitem = j;
ytplayer_playlazy(1000);
};
})(i);
What you have here is a self-invoking anonymous function. Let's break it down, first replacing the body of the function with something simpler (return j + 1;):
function( j ) { return j + 1; }
This s a run-of-the-mill anonymous function or closure. This line of code is an expression, and so it has a value, and that value is a function. Now we could do this:
var foo = function( j ) { return j + 1; }
foo( 5 ); // => 6
You recognize this, I'm sure—we're assigning the anonymous function to the variable foo, and then calling the function by name with the argument i. But, instead of creating a new variable, because the closure is an expression we can call it like this instead:
( function( j ) { return j + 1; } )( 5 ); // => 6
Same result. Now, it's just returning j + 1 but in your code it returns something else: Another anonymous function:
return function() { /* ... */ }
What happens when we have a self-invoking anonymous function that returns a function? The result is the "inner" function that was returned:
a.onclick = ( function( j ) {
return function() {
ytplayer_playitem = j;
ytplayer_playlazy( 1000 );
}
}
)( i );
If i was equal to 9 then a.onclick would now hold a function equivalent to this:
function() {
ytplayer_playitem = 9;
ytplayer_playlazy( 1000 );
}
As others have pointed out, the usefulness of this is that when ( function( j ) { /* ... */ } )( i ) is invoked you are capturing the value of i at that time and putting it into j rather than creating a reference to the value i holds, which may (and probably will) change later on.

Javascript scope and calling a function

My code:
for (var i = 0; i < mapInfos.length; i++) {
var x = function () { doStuff(i); };
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
The doStuff method simply alerts the value of i. mapInfos has two entries, so you'd expect it to alert 0 and 1, but instead it alerts 2 and 2. I can appreciate vaguely why it is doing this (although var i should keep it local to the scope of the loop?) but how can I make it work as intended?
edit — note that when first posted, the original question included a link to a jsfiddle that seemed to be a relevant example of what the current question is trying to achieve, only it appears to work ...
The code in the jsfiddle works because there's only one "i" in that code. The "i" used in the second loop (where the functions are actually called) is the same "i" as used in the first loop. Thus, you get the right answer because that second loop is running "i" through all the values from zero through four again. If you added:
i = 100;
functions[0]();
you'd get 100 printed out.
The only way to introduce a new scope in JavaScript is a function. One approach is to write a separate "function maker" function:
function makeCallback(param) {
return function() {
doStuff(param);
};
}
Then in your loop:
for (var i = 0; i < mapInfos.length; i++) {
var x = makeCallback(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'titlesloaded', x);
}
That'll work because the call to the "makeCallback" function isolates a copy of the value of "i" into a new, unique instance of "param" in the closure returned.
Create a new scope for it.
Functions create scope.
function doStuffFactory(i) {
return function () { doStuff(i); };
}
for (var i = 0; i < mapInfos.length; i++) {
var x = doStuffFactory(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
Change it to
var x = function (param) { doStuff(param); };
Obviously what is going on is that you are alerting a variable that is changing. With the above change it copies it so even if i changes it will still alert the right value.
Javascript doesn't have block scope, so you don't get an x that's local to the loop. Yea!
It has function scope, though.
Yep, weird isn't it!Pointy has an explanation
I have no idea why your first example worked (I wasn't expecting it to) Pointy has an explanation of why your first example worked - The reason why your second one doesn't is because i is scoped to the function containing the for loop, not to the scope defined by the for loop. In fact the only things that have scope in JavaScript are functions. This means that by the time your function gets executed i is 2.
What you need to do is create a scope, for example:
for (var i = 0; i < mapInfos.length; i++) {
var x = (function() {
return function () { doStuff(i); };
})(i);
google.maps.event.addListenerOnce(mapInfos[i].map, 'tilesloaded', x);
}
See JavaScript closures in for-loops for more.

Javascript Closure Problem

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.

Categories