Vote count:
615
Closures are one of those things that have been discussed a lot on SO, but this situation pops up a lot for me and I'm always left scratching my head.
var funcs = {};
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
It outputs this:
My value: 3
My value: 3
My value: 3
Whereas I'd like it to output:
My value: 0
My value: 1
My value: 2
What's the solution to this basic problem?
21 Answers
Vote count:
532
accepted
Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.
What you want to do is bind the variable within each function to a separate, unchanging value outside of the function:
var funcs = [];
function createfunc(i) {
return function() { console.log("My value: " + i); };
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Since there is no block scope in JavaScript - only function scope - by wrapping the function creation in a new function, you ensure that the value of "i" remains as you intended.
Vote count:
143
Try:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Edit (2014):
Personally I think @Aust's more recent answer about using .bind
is the best way to do this kind of thing now. There's also lo-dash/underscore's _.partial
when you don't need or want to mess with bind
's thisArg
.
Vote count:
65
Using an Immediately-Invoked Function Expression, IIFE jQuery Docs, the simplest and most readable way to enclose an index variable:
for (var i = 0; i < 3; i++) {
(function(index) {
console.log('iterator: ' + index);
//now you can also loop an ajax call here without problems: $.ajax({});
})(i);
}
This sends the iterator i
into the anonymous function of which we define as index
. This creates a closure, where the variable i
gets saved for later use in any asynchronous functionality within the IIFE.
Vote count:
49
Another way that hasn't been mentioned yet is the use of Function.prototype.bind
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Vote count:
24
Another way of saying it is that the i in your function is bound at the time of executing the function, not the time of creating the function.
When you create the closure, i is a reference to the variable defined in the outside scope, not a copy of it as it was when you created the closure. It will be evaluated at the time of execution.
Most of the other answers provide ways to work around by creating another variable that won't change value on you.
Just thought I'd add an explanation for clarity. For a solution, personally I'd go with Harto's since it is the most self explanatory way of doing it from the answers here. Any of the code posted will work, but I'd opt for a closure factory over having to write a pile of comments to explain why I'm declaring a new variable(Freddy and 1800's) or have weird embedded closure syntax(apphacker).
Vote count:
20
What you need to understand is the scope of the variables in javascript is based on the function. This is an important difference than say c# where you have block scope, and just copying the variable to one inside the for will work.
Wrapping it in a function that evaluates returning the function like apphacker's answer will do the trick, as the variable now has the function scope.
There is also a let keyword instead of var, that would allow using the block scope rule. In that case defining a variable inside the for would do the trick. That said, the let keyword isn't a practical solution because of compatibility.
var funcs = {};
for (var i = 0; i < 3; i++) {
let index = i; //add this
funcs[i] = function() {
console.log("My value: " + index); //change to the copy
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Vote count:
16
This describes the common mistake with using closures in JavaScript.
A function defines a new environment
Consider:
function makeCounter()
{
var obj = {counter: 0};
return {
inc: function(){obj.counter ++;},
get: function(){return obj.counter;}
};
}
counter1 = makeCounter();
counter2 = makeCounter();
counter1.inc();
alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0
For each time makeCounter
is invoked, {counter: 0}
results in a new object being created. Also, a new copy of obj
is created as well to reference the new object. Thus, counter1
and counter2
are independent of each other.
Closures in loops
Using a closure in a loop is tricky.
Consider:
var counters = [];
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
}
makeCounters(2);
counters[0].inc();
alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1
Notice that counters[0]
and counters[1]
are not independent. In fact, they operate on the same obj
!
This is because there is only one copy of obj
shared across all iterations of the loop, perhaps for performance reasons. Even though {counter: 0}
creates a new object in each iteration, the same copy of obj
will just get updated with a reference to the newest object.
Solution is to use another helper function:
function makeHelper(obj)
{
return {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = makeHelper(obj);
}
}
This works because local variables in the function scope directly, as well as function argument variables, are allocated new copies upon entry.
For a detailed discussion, please see JavaScript closure pitfalls and usage
Vote count:
14
With ES6 around the corner, note that the correct answer to this question will be changing. ES6 provides the let
keyword for this exact circumstance. Instead of messing around with closures, we can just use let
to set a loop scope variable like this:
var funcs = {};
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
val
will then point to an object that is specific to that particular turn of the loop, and will return the correct value without the additional closure notation. This obviously significantly simplifies this problem.
Browser support is currently sketchy, but let
is currently supported in the latest version of firefox (21.0) and Dev builds of chrome. You can see a working example here if you have a compatible browser: http://ift.tt/1C5AuPz
Vote count:
13
Here's another variation on the technique, similar to Bjorn's (apphacker), which lets you assign the variable value inside the function rather than passing it as a parameter, which might be clearer sometimes:
for (var i = 0; i < 3; i++) {
funcs[i] = (function() {
var index = i;
return function() {
console.log("My value: " + index);
}
})();
}
Note that whatever technique you use, the index
variable becomes a sort of static variable, bound to the returned copy of the inner function. I.e., changes to its value are preserved between calls. It can be very handy.
Vote count:
9
The most simple solution would be
instead of using this
var funcs = [];
for(var i =0; i<3; i++){
funcs[i] = function(){
alert(i);
}
}
for(var j =0; j<3; j++){
funcs[j]();
}
which alerts 2, 3 times. Use this,
var funcs = [];
for(var new_i =0; new_i<3; new_i++){
(function(i){
funcs[i] = function(){
alert(i);
}
})(new_i);
}
for(var j =0; j<3; j++){
funcs[j]();
}
The idea behind this is, encapsulating the entire body of the for loop with a self calling anonymous function and passing "new_i" as a parameter and capturing it as "i". Since the anonymous function is executed immediately, the "i" value is different for each function defined inside anonymous function. This solution seems to fit any such problem, since it will require minimum changes to original code suffering for this issue. In fact this is by design, it should not be an issue at all!
Vote count:
6
no array
no extra for loop
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
Vote count:
5
The main issue with the code shown by the OP is that i
is never read until the second loop. To demonstrate, imagine seeing an error inside of the code
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
The error actually does not occur until funcs[someIndex]
is executed ()
. Using this same logic, it should be apparent that the value of i
is also not collected until this point either. Once the original loop finishes, i++
brings i
to the value of 3
which results in the condition i < 3
failing and the loop ending. At this point, i
is 3
and so when funcs[someIndex]()
is used, and i
is evaluated, it is 3 - every time.
To get past this, you must evaluate i
as it is encountered. Note that this has already happened in the form of funcs[i]
(where there are 3 unique indexes). There are several ways to capture this value. One is to pass it in as a parameter to a function which is shown in several ways already here.
Another option is to construct a function object which will be able to close over the variable. That can be accomplished thusly
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
Vote count:
2
Here's a simple solution that uses forEach
(works back to IE9):
var funcs = {};
[0,1,2].forEach(function(i) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
})
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Prints:
My value: 0
My value: 1
My value: 2
Vote count:
2
After reading through various solutions provided, I'd like to add that the reason those solutions work is to rely on the concept of scope chain. In short, each function definition forms a scope mainly consisting of all the local variables declared by var
and its arguments
. When a function gets executed, it evaluates variables by searching the scope chain. If a variable can be found in a certain point of the chain it will stop searching and use it, otherwise it continues until the global scope which belongs to window
.
In your initial code:
funcs = {};
for (var i = 0; i < 3; i++) { // assume 'for' is not contained in any function
funcs[i] = function() { // function inner's scope contains nothing
console.log("My value: " + i);
};
}
console.log(window.i) // test value 'i', print 3
When funcs
gets executed, the scope chain will be function inner -> global
. Since the variable i
cannot be found in function inner
(neither declared using var
nor passed as arguments), it continues to search. So the value of i
is eventually evaluated as the one that belongs to the global which is window.i
.
By wrapping it in an outer function either explicitly define a helper function like harto did or use an anonymous function like Bjorn did:
funcs = {};
function createfunc(i) { // function outer's scope contains 'i'
return function() { // function inner, closure created
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
when funcs
gets executed, now the scope chain will be function inner -> function outer
. This time i
can be found in the outer function which is the value of argument (this value is correctly bound in the for
loop). It won't use the value of window.i
You can read this: http://ift.tt/1cWVtVL. It includes the common mistake in creating closure in the loop as what we have here, as well as why we need closure and the performance consideration.
Vote count:
2
I'm surprised no one yet have suggested using the map function to better avoid (re)using local variables. In fact, I'm not using for(var i ...)
at all anymore for this reason.
[0,2,3].map(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3
Vote count:
0
so the reason your original example did not work is that all the closures you created in the loop referenced the same frame. in effect having 3 methods on one object with only a single 'i' variable. they all printed out the same value
Vote count:
0
well, here you are only defining the three functions inside the for loop.They will be executed in the next for loop and at that time value of i is same(3) for each loop. Thats why ou are getting 3 as your output.
Your output has nothing to do with closure here you can simply write it as,
var funcs = {};
funcs = function getValue(x) {
document.write("My value: " + x);
};
for (var j = 0; j < 3; j++) {
funcs(j);
}
Vote count:
0
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
}
})(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Vote count:
0
Try it:
var func=[];
for(var i=0; i<3; i++){
func[i]=function createfunc(i){
return function(){console.log("I am : " +i);
};
}(i);
}
for(var j=0; j<3; j++){
func[j]();
}
Vote count:
-1
The value i
inside the body of the closure is being bound to the same instance for each closure (that is, to the variable i
which is the loop control variable for the for loop). This value is changing as you go through the loop. You need to figure out a way to make sure that it is bound to a different variable that is unique for each closure. The method shown by apphacker is one way, although possibly a little arcane.
var funcs = {};
for (var i = 0; i < 3; i++) {
var index = i; // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + index); // each should log its value.
};
}
This method doesn't work - the reason being because var
in javascript uses function scope as opposed to other algol derivatives which use block scope. You can use let
as a slightly less portable alternative, but it looks like your best bet is to use a factory method to create the closures.
Vote count:
-2
You can make it by recursive function, like that
function hellowfunction(i){
var j=0; //make our iterator
(function onetick(){
if(j<i){ //checking state
setTimeout(function(){
console.log(j++); //increment the value
onetick(); //call function again
},1000) //Waiting one second for above function
}
})(); //Run function first time after creation
};
hellowfunction(4); // Turn it for 0..(sec)..1..2..3
improvement of this method is that you do not create a bunch of function with setTimeout at the beginning
JS Button Onclick In For Loop [duplicate]
Aucun commentaire:
Enregistrer un commentaire