This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I'm trying to grasp this bit of code, I know it's a closure but I'm not getting the result I think I should get.
This code returns me an [object MouseEvent], I can't understand why?
I'm adding a function call (updateProduct) to an .addEventListener using this code, and it returns an [object MouseEvent]
function addEventListenerToMinPlus(){
var x, y
for(var i = 0; i < productItemAll.length; i++){
x = productItemAll[i].querySelector(".boxNumbers-min")
x.addEventListener("click", function(i){return function(i){updateProduct(i)}}(i))
console.log(x)
}
}
function updateProduct(jow){
alert(jow)
}
jsFiddle
The browser invokes the event handler with an event object as the first parameter. Your function is declared to take a single parameter ("i"), so when you display it, that's what it is.
I suspect that what you meant was for the "i" inside the event handler to refer to the "i" in the outer function (the loop index). That also won't work, because the various handlers the loop creates will all refer to the same shared variable "i". See this old SO question.
The line
x.addEventListener("click", function(i) { return function(i) { updateProduct(i); }(i) }
produces a closure of the inner function
function(i) { updateProduct(i); }
The outer i is in the scope of this inner function, but it is shadowed by its parameter. So, in effect, the inner i represents the first argument passed to the click handler (the MouseEvent). If you want it to retain the value of the index, you have to change its name. Something like this:
x.addEventListener("click",
function(i) { return function(e) { updateProduct(i); }(i)
}
Now, in the inner function, e is the MouseEvent, and i is the outer index. I have updated the JSFiddle: http://jsfiddle.net/Cdedm/2/. Clicking the minus alerts 0 for the first item and 1 for the second, as expected.
that is because you are sending the element as parameter.
You should try doing this:
function addEventListenerToMinPlus(){
var x, y
for(var i = 0; i < productItemAll.length; i++){
x = productItemAll[i].querySelector(".boxNumbers-min")
x.addEventListener("click", function(){return updateProduct(i)})
console.log(x)
}
}
Hope this works,
Regards,
Marcelo
I think you are trying to do something like this. Your i will change by the time the click happens so it needs to be set to another local variable, in this case through the parameter on the new function. The click event handler will pass the event object you are getting currently
function addEventListenerToMinPlus() {
var x, y;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.addEventListener("click", function(i){return function(){updateProduct(i)}}(i));
}
}
function updateProduct(jow) {
alert(jow);
}
Unless you really really really know what you're doing, you can play about with closures all day and still not get it right with this sort of thing.
A more understandable appraoch by far, with more readable code for most people, is to use jQuery's .data() method to associate data (i in this case) with the element in question so it can be read back when the click event fires, for example :
function addEventListenerToMinPlus() {
var x, y;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.data('myIndex', i);//associate `i` with the element
x.addEventListener("click", function(i) {
var i = $(this).data('myIndex');//read `i` back from the element
updateProduct(i);
});
console.log(x);
}
}
For the record, a working closure would be as follows :
function addEventListenerToMinPlus() {
var x;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.addEventListener("click", function(i) {//<<<< this is the i you want
return function() {//<<<< NOTE: no formal variable i here. Include one and you're stuffed!
updateProduct(i);
}
}(i));
console.log(x);
}
}
Related
I was asked the below question during an interview, and I still couldn't get my head around it, so I'd like to seek your advice.
Here's the question:
var countFunctions = [];
for(var i = 0; i < 3; i++){
countFunctions[i] = function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
//The below are executed in turns:
countFunctions[0]();
countFunctions[1]();
countFunctions[2]();
When asked what would be the output of the above, I said count0,count1 and count2 respectively. Apparently the answer was wrong, and that the output should all be count3, because of the concept of closures (which I wasn't aware of then). So I went through this article and realized that I should be using closure to make this work, like:
var countFunctions = [];
function setInner(i) {
return function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
for(var i = 0; i < 3; i++){
countFunctions[i] = setInner(i);
}
//Now the output is what was intended:
countFunctions[0]();//count0
countFunctions[1]();//count1
countFunctions[2]();//count2
Now that's all well and good, but I remember the interviewer using something simpler, using a self-executing function like this:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(){
document.getElementById('someId').innerHTML = 'count' + i;
})(i);
}
The way I understand the above code, we are skipping the declaration of a separate function and simply calling and executing the function within the for loop.
But when I ran the below:
countFunctions[0];
countFunctions[1];
countFunctions[2];
It didn't work, with all the output being stuck at count2.
So I tried to do the below instead:
for(var i = 0; i < 3; i++) {
countFunctions[i] = function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
, and then running countFunctions[0](), countFunctions[1]() and countFunctions[2](), but it didn't work. The output is now being stuck at count3.
Now I really don't get it. I was simply using the same line of code as setInner(). So I don't see why this doesn't work. As a matter of fact, I could have just stick to the setInner kind of code structure, which does work, and is more comprehensive. But then I'd really like to know how the interviewer did it, so as to understand this topic a little better.
The relevant articles to read here are JavaScript closure inside loops – simple practical example and http://benalman.com/news/2010/11/immediately-invoked-function-expression/ (though you seem to have understood IEFEs quite well - as you say, they're "skipping the declaration of a separate function and simply calling and executing the function").
What you didn't notice is that setInner does, when called, return the closure function:
function setInner(i) {
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
// then do
var countFunction = setInner("N"); // get the function
countFunction(); // call it to assign the innerHTML
So if you translate it into an IEFE, you still need to create (and return) the function that will actually get assigned to countFunctions[i]:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(i){
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
})(i);
}
Now, typeof countFunctions[0] will be "function", not "undefined" as in your code, and you can actually call them.
Take a look at these four functions:
var argument = 'G'; //global
function passArgument(argument){
alert(argument); //local
}
function noArguments(){
alert(argument); //global
}
function createClosure_1(argument){
return function (){
alert(argument); //local
};
}
function createClosure_2(argument){
var argument = argument; //local
return function (){
alert(argument); //local
};
}
passArgument('L'); //L
noArguments(); //G
createClosure_1('L') //L
createClosure_2('L') //L
alert(argument) //G
I think, first function is obvious.
In function noArguments you reference the global argument value;
The third and fourth functions do the same thing. They create a local argument variable that doesn't change inside them and return a function that references that local variable.
So, what was in the first and the last code snippet of your question is a creation of many functions like noArguments,
that reference global variable i.
In the second snippet your setInner works like createClosure_1. Within your loop you create three closures, three local variables inside them. And when you call functions inside countFunctions, they get the value of the local variable that was created inside the closure when they were created.
In the third one you assign the result of the execution of those functions to array elements, which is undefined because they don't return anything from that functions.
I am trying to do a seemingly trivial thing, but I cant figure this out. I am iterating over items found by the document.getElementByClassName method. I am doing so with indices so I can keep track of some stuff, and I need that index inside the onmousedown events for that specific element, however I can't figure out to do so.
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--)
{
items[i].onmousedown=function(){
//This does not work:
var index = i; //I need the i variable from the loop above in here.
console.log(index);
this.innerHTML = doSomeWorkWith(index);
};
}
Anyone know how to do this? I have thought of adding it to the element itself so I can access a variable there, but I would prefere not to as it would clutter the html code.
You need to keep your indexes in closure, something as
for (var i = items.length - 1; i >= 0; i--){
(function(index){
...do anithing
})(i);
}
You'll need to create the handler functions on the fly, using another function. That can easily be done using immediately invoced function expressions (IIFEs). That way, you'll get the i to be evaluated when defining the handler, not when executing it.
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--) {
items[i].onmousedown = (function (index) {
return function () {
console.log(index);
this.innerHTML = doSomeWorkWith(index);
}
})(i);
}
Basically, I'm not directly assigning a function to onmousedown, but creating one on the fly that has the value of i hardcoded.
To create that handler function, I'm using another function, that I immediately (in-place) invoke after defining it, without ever assigning a name. (Of course I just could create that function in global scope and us it here, but as I don't need it anywhere else, why should I?)
[Edit]: To use the event inside that function, use
var items = document.getElementsByClassName("someClass");
for (var i = items.length - 1; i >= 0; i--) {
items[i].onmousedown = (function (index) {
return function (event) {
this.innerHTML = doSomeWorkWith(index);
// do something with "event" here
}
})(i);
}
This is a classical problem, the anonymous function captures the variable and not its value, so when it is indeed called, the current value is not correct.
See this link for more information : Arguments to JavaScript Anonymous Function
I want to use event listeners to prevent event bubbling on a div inside a div with onclick functions. This works, passing parameters how I intended:
<div onclick="doMouseClick(0, 'Dog', 'Cat');" id="button_id_0"></div>
<div onclick="doMouseClick(1, 'Dog', 'Cat');" id="button_id_1"></div>
<div onclick="doMouseClick(2, 'Dog', 'Cat');" id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
</script>
However, I tried to create multiple event listeners in a loop with this:
<div id="button_id_0"></div>
<div id="button_id_1"></div>
<div id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", function(){
doMouseClick(i, "Dog", "Cat");
},false);
}
</script>
It correctly assigns the click function to each div, but the first parameter for each, peram1, is 3. I was expecting 3 different event handlers all passing different values of i for peram1.
Why is this happening? Are the event handlers not all separate?
Problem is closures, since JS doesn't have block scope (only function scope) i is not what you think because the event function creates another scope so by the time you use i it's already the latest value from the for loop. You need to keep the value of i.
Using an IIFE:
for (var i=0; i<names.length; i++) {
(function(i) {
// use i here
}(i));
}
Using forEach:
names.forEach(function( v,i ) {
// i can be used anywhere in this scope
});
2022 edit
As someone is still reading and upvoting this answer 9 years later, here is the modern way of doing it:
for (const [i, name] of names.entries()) {
document.getElementById(name).addEventListener("click", () => doMouseClick(i, "Dog", "Cat"), false);
}
Using const or let to define the variables gives them block-level scope and the value of i passed to the handler function is different for each iteration of the loop, as intended.
The old ways will still work but are no longer needed.
2013 answer
As pointed out already the problem is to do with closures and variable scope. One way to make sure the right value gets passed is to write another function that returns the desired function, holding the variables within the right scope. jsfiddle
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function getClickFunction(a, b, c) {
return function () {
doMouseClick(a, b, c)
}
}
for (var i = 0; i < names.length; i++) {
document.getElementById(names[i]).addEventListener("click", getClickFunction(i, "Dog", "Cat"), false);
}
And to illustrate one way you could do this with an object instead:
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function Button(id, number) {
var self = this;
this.number = number;
this.element = document.getElementById(id);
this.click = function() {
alert('My number is ' + self.number);
}
this.element.addEventListener('click', this.click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
or slightly differently:
function Button(id, number) {
var element = document.getElementById(id);
function click() {
alert('My number is ' + number);
}
element.addEventListener('click', click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
It's because of closures.
Check this out: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake
The sample code and your code is essentially the same, it's a common mistake for those don't know "closure".
To put it simple, when your create a handler function, it does not just accesses the variable i from the outer environment, but it also "remembers" i.
So when the handler is called, it will use the i but the variable i is now, after the for-loop, 2.
I've been struggling with this problem myself for a few hours and now I've just now managed to solve it. Here's my solution, using the function constructor:
function doMouseClickConstructor(peram1, peram2, peram3){
return new Function('alert("doMouseClick() called AND peram1 = ' + peram1 + ' AND peram2 = ' + peram2 + ' AND peram3 = ' + peram3 + ');');
}
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", doMouseClickConstructor(i,"dog","cat"));
};
Note: I havn't actually tested this code. I have however tested this codepen which does all the important stuff, so if the code above doesn't work I've probably just made some spelling error. The concept should still work.
Happy coding!
Everything is global in javascript. It is calling the variable i which is set to 3 after your loop...if you set i to 1000 after the loop, then you would see each method call produce 1000 for i.
If you want to maintain state, then you should use objects. Have the object have a callback method that you assign to the click method.
You mentioned doing this for event bubbling...for stopping event bublling, you really do not need that, as it is built into the language. If you do want to prevent event bubbling, then you should use the stopPropagation() method of the event object passed to the callback.
function doStuff(event) {
//Do things
//stop bubbling
event.stopPropagation();
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I have an array of 4 objects (that.pairs), and each object has a .t property which is a jQuery object/element. I'm trying to set an event on each t being clicked.
The problem is that when one of the them gets clicked, it's always the last pair (index 3) that gets passed into my doToggle() function.
Why is this happening? How can I fix it?
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
p.t.click(function() {
that.doToggle(p);
});
}
It's because the p variable is shared by your closures, there's just one p variable. By the time your handlers are called, p has changed.
You have to use a technique I call freezing your closures
for (var i = 0; i < that.pairs.length; i++) {
// The extra function call creates a separate closure for each
// iteration of the loop
(function(p){
p.t.click(function() {
that.doToggle(p);
});
})(that.pairs[i]); //passing the variable to freeze, creating a new closure
}
A easier to understand way to accomplish this is the following
function createHandler(that, p) {
return function() {
that.doToggle(p);
}
}
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
// Because we're calling a function that returns the handler
// a new closure is created that keeps the current value of that and p
p.t.click(createHandler(that, p));
}
Closures Optimization
Since there was a lot of talk about what a closure is in the comments, I decided to put up these two screen shots that show that closures get optimized and only the required variables are enclosed
This example http://jsfiddle.net/TnGxJ/2/ shows how only a is enclosed
In this example http://jsfiddle.net/TnGxJ/1/, since there's an eval, all the variables are enclosed.
Use $.each instead of a for loop so that you get a new variable scope with each iteration.
$.each(that.pairs, function(i, p) {
p.t.click(function() {
that.doToggle(p);
});
});
This way each click handler closes over a unique variable scope instead of the shared outer variable scope.
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
(function(p){
p.t.click(function() {
that.doToggle(p);
});
}(p));
}
This trick with IIFE would solve the closure "issue" you're experiencing now.
for (var i = 0; i < that.pairs.length; i++) {
(function(num){
var p = that.pairs[num];
p.t.click(function() {
that.doToggle(p);
});
})(i)
}
Classic closure issue
Enclose them in an anonymous function and assign the current iteration in context. That should solve the problem..
I'm having trouble resolving a scope issue with my javascript.
I have an array, dog[] that is defined from JSON, that I need access to from inside a nested function.
function blah(json) {
for (var u = 0; u < json[0][1][u].length; u ++ ) {
var dog = 'k' + json[0][1][u].doggies;
console.log(dog); // prints array of doggie strings
$('#puppy').click(function(dog) { // dog is passed in the function
console.log(dog); // Syntax error, unrecognized expression: #[object Object]
$('#' + dog).css('display, 'none');
});
}
}
when I dont pass dog into the click function: i get:
$('#puppy').click(function() {
console.log(dog) // (12) main.js:122k4c812e3a7275e10331000000 - this is the last value in the array - from safari console
$('#' dog).css('display', 'none);
}
Does anyone have any suggestions to get the array with every element passed into the click function?
Or am i calling the css method incorrectly to hide those divs?
Problem 1
Closures bind the entire function's scope, and not individual variables or values.
Take this code for example:
function foo() {
var i, func;
for (i = 0; i < 10; ++i) {
if (i == 0) {
func = function () {
alert(i);
}
}
}
func();
}
foo();
You may expect foo to cause 0 to be alerted. However, the value of i has changed since the function assigned to func was created; the call to func alerts "10".
Here is another example illustrating the concept:
function foo() {
var i = 42;
function func() {
alert(i);
}
for (i = 0; i < 10; ++i) {
// do nothing
}
func();
}
foo();
Try to figure out what will be alerted, and run the code as a test.
Problem 2
The second problem is that variables are bound at the function scope (and not the block scope as you expect).
Take this code:
function foo() {
var i;
for (i = 0; i < 10; ++i) {
var j = i;
}
alert(j);
}
foo();
You may expect this code to alert "undefined", throw a run-time error, or even throw a syntax error. However, "10" is alerted. Why? In JavaScript, the above code is translated into effectively:
function foo() {
var i;
var j;
for (i = 0; i < 10; ++i) {
j = i;
}
alert(j);
}
foo();
It should be more clear from this example that "10" is indeed alerted.
Solution
So how do you fix your problem? The simplest way is to change your logic: instead of attaching one event handler per dog, attack one event handler per collection of dogs. For example:
function blah(json) {
$('#puppy').click(function () {
var u, dog;
for (u = 0; u < json[0][1][u].length; u++) {
dog = 'k' + json[0][1][u].doggies;
console.log(dog);
$('#' + dog).css('display', 'none');
}
});
}
If you're interested in the "proper" transformation of your existing code (i.e. having the same behaviours, except with the bug fixed), I can give you an example of that as well. However, the solution I gave above is a much better solution and results in cleaner code.
Important Note:
You forgot to close your quote. This:
$('#' + dog).css('display, 'none');
Should be:
$('#' + dog).css('display', 'none');
An Improved Loop:
There are several problems with your script. I'll concentrate on the overall logical structure of the loop.
Instead of attaching many handlers to .click(), just attach one handler that iterates over you JSON using jQuery's .each(). The first argument of the callback of .each() is the index number and the second argument is the value. You can make use of those 2 by naming the arguments or by using arguments[0] and arguments[1]. I show the former method below:
I've added some more test output for demonstration purposes:
function blah(json) {
$('#puppy').click(function() {
// iterate over each json[0][1]
$.each(json[0][1], function(index, value) {
// Your original 2 lines
console.log(value);
$('#' + value).css('display', 'none');
// This is just test output, so you can see what is going
// on.
$("body").append("Number " + index + " is " + value ".<br/>");
});
});
}
Why not just give the doggies a class .dog and hide them when #puppy is clicked?
$("#puppy").click(function() {
$(".dog").hide();
});
Or since your dog's IDs seem to start with k, you might consider something like this:
$("#puppy").click(function() {
// hide everything with ID beginning with 'k'
$('[id^=k]').hide();
});
You can't pass the dog value into the jquery click event as you have done there. The click function signature is:
$(object).click(function(){
});
You can't pass dog in like this. Even if the function expected a parameter, naming it dog would cause issues. You may need to store the values of dog in a more global scope so that when the click event occurs, you still have access to it.