I'm adding an event listener to some elements I'm looping through and need a closure in order to preserve the index in the event function.
<button>solution 1</button>
<button>solution 2</button>
<script>
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < 3; i++) {
var log = (function closure(number) {
return function () {
console.log(number);
};
})(i);
buttons[0].addEventListener("click", log);
}
for (var i = 0, len = 3; i < len; i++) {
(function (i) {
var log = function () {
console.log(i);
};
buttons[1].addEventListener("click", log);
})(i);
}
</script>
http://jsfiddle.net/paptd/11/
Both these solutions output 0, 1, 2 correctly (try 'wrong' to see what happens without a closure) but I'm trying to understand which one I should use and why.
Which way is the correct way of doing it?
The first one works because you are defining a closure, returning a function from it, then assigning that function to a listener.
The second one seems more proper, since the closure encompasses the entire loop content, making it more obvious that the value of i is to be "locked" there.
You shouldn't use any of these--you're creating n identical functions inside of your loop. You should refactor your code into a named function that returns the event handler:
var buttons = document.getElementsByTagName('button');
function createHandler(number) {
return function () {
console.log(number);
};
}
for (var i = 0; i < 3; i++) {
buttons[0].addEventListener("click", createHandler(i));
}
Example: http://jsfiddle.net/paptd/12/
Related
i have this code and this is working:
var wrap_inner = document.getElementsByClassName("wrapper"),
base_icon = document.getElementsByClassName("inner");
function fx_effect () {
for (var i = 0; i < wrap_inner.length; i++) {
(function (index) {
wrap_inner[i].addEventListener('click',function() {
base_icon[index].classList.toggle('tc');
});
})(i); <==== what is that? what do that?
}
}fx_effect();
but i don't know what is the index in: function (index) { ...
and what is (i) at the end of function.
Why index is used? What is its value?
why (i) at the end of function used like this?
When should be used in this way?
why the function like this don't work???:
function fx_effect () {
for (var i = 0; i < wrap_inner.length; i++) {
wrap_inner[i].addEventListener('click',function() {
base_icon[i].classList.toggle('tc');
});
}
}fx_effect();
I'm confused :(
The concept is javascript closures . First method is creating a closure ( a scope or a stack as analogy) for each value passed. So that when fx_effect is called, the inner method runs for values 0 to wran_inner.length distinctively as each value is in a different closure.
For second method, the scope will have just the final value, hence calling the method fx_effect will result in the loop running that method for final value of i, i.e. wrap_inner.length - 1
I have a loop where I define a closure for an event:
for (var i = 0; i < 3; i++) {
obj.onload = function(e) {
me.myFunction(e, i);
};
}
I need to pass that counter variable to the function that is called when the load event is triggered. I always get the same value being passed.
How can I pass the right counter value to the function?
The usual way to solve this is with a builder function, so that the handler closes over something that doesn't change:
function buildHandler(index) {
return function(e) {
me.myFunction(e, index);
};
}
for (var i = 0; i < 3; i++) {
obj.onload = buildHandler(i);
}
You can do that with an immediately-invoked function expression (IIFE), but in theory it creates and throws away a function on every loop (I expect most JavaScript engines these days optimize that) and I, at least, think it's a lot harder to read:
for (var i = 0; i < 3; i++) {
obj.onload = (function(index) {
return function(e) {
me.myFunction(e, index);
};
})(i);
}
There's an ES5 feature, Function#bind, that you can use if you want to use me.myFunction directly, but you'd have to change the order in which you were expecting the arguments (index first, then the event):
for (var i = 0; i < 3; i++) {
obj.onload = me.myFunction.bind(me, i);
}
bind creates a function that, when called, calls the original function with this set to the first argument (so, me above), then any further arguments you give it, and then any arguments that were used when calling the function. (That's why the order myFunction was expecting them had to change to index, e rather than e, i.)
On older browsers, you need a shim for Function#bind.
I've declared the variable elem in both of the loops below; however, when the anonymous function in the first loop gets called (after the 400ms fadeOut effect is finished) the elem seems to refer to the value of elem which was assigned in the second loop. In other words, the code works correctly if you rename elem in the second loop to any other variable name.
Is there a way to make a closure around the anonymous function so that the value of elem is not changed in the context of the anonymous function?
for (var i = 0; i < outs.length; i++) {
var elem = this.elementAtPoint(outs[i]);
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
//$(elem).css("background", "red");
}
for (var i = 0; i < ins.length; i++) {
var elem = this.elementAtPoint(ins[i]);
var piece = this.board.pieceAt(ins[i]);
$(elem).hide();
$(elem).addClass(this.classForPiece(piece));
$(elem).fadeIn(400);
}
You could use a anonymous self executing function
for (var i = 0; i < outs.length; i++) {
(function(elem){
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
//$(elem).css("background", "red");
})(this.elementAtPoint(outs[i]));
}
in javascript, variable scope is decided by function, so you can wrap the two for loops in two function. a wrapper function example:
(function(){
//put you for loop here
})();
I would avoid creating lots of closures inside loops, they would be inefficient as they create copies of the current scope. Instead I'd simply create a couple of extra functions then call them inside your loops:
var fadeInPiece = function(elem, piece){
$(elem).hide();
$(elem).addClass(this.classForPiece(piece));
$(elem).fadeIn(400);
}
var fadeOutElem = function(elem){
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
}
for (var i = 0; i < outs.length; i++)
fadeOutElement(this.elementAtPoint(outs[i]));
for (var i = 0; i < ins.length; i++)
fadeInPiece(this.elementAtPoint(ins[i]), this.board.pieceAt(ins[i]));
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);
}
This should be easy but my head seems to be frazzled just now.
The result I'm aiming for is 0,1,2,0,1,2
My code so far.... here is a fiddle to play with.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout(function(){
delayedLoad(i);
},3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
This is a very common issue people hit.
The trouble is that the function you're passing to setTimeout in each iteration is referencing the same i variable.
JavaScript does not have block scope, only function scope. So to create a new scope that will retain the i value you want, you need to invoke a function inside the loop, passing it i.
Example: http://jsfiddle.net/GNwhR/3/
function set_up_timeout( j ) {
setTimeout(function(){
delayedLoad( j );
},3000*j);
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
set_up_timeout( i );
}
};
This places your setTimeout call in another function which is invoked and passed i in each iteration. This creates a new variable scope where j in the function references the proper value.
Another approach is to have the invoked function return a function:
Example: http://jsfiddle.net/GNwhR/4/
function set_up_callback( j ) {
return function(){
delayedLoad( j );
};
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout( set_up_callback( i ), 3000*i );
}
};
This one calls set_up_callback, which returns a function that references j.
Finally, another common approach is to use an IIFE (immediately invoked function expression) to eliminate the named function. This is less clear IMO:
Example: http://jsfiddle.net/GNwhR/5/
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout((function( j ){
return function() {
delayedLoad( j );
};
})( i ),3000*i);
}
};
This is basically the same as the previous example in that it returns a function. The main difference is that the function that returns the function is created and invoked inside the loop itself.
All of these illustrate the same concept, that you need to scope a variable if you wish to asynchronously reference it later and have it retain its expected value.
patrick's explanation is pretty well right on. Here's a bit of code to help you get around it.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log('in loop', i);
setTimeout($.proxy(function(){
delayedLoad(this.page);
}, {page: i}),3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
jQuery's proxy just resets what 'this' is pointing to in a function.. so, you can replace passing in a number to just resetting the scope :) (if that makes any sense.. long day)