This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
Here is a piece of code :
// -------- Sample 1 ---------------
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
// --------- Sample 2 --------------
var fun = [];
for(var i = 0;i < 3; i++) {
fun[i] = function() {
console.log('values is '+ i);
}
}
for(var i = 0; i < 3; i++) {
fun[i]();
}
Both the sample codes are same except the variable names. But it's giving different output. The output of the above code is :
My value: 3
My value: 3
My value: 3
values is 0
values is 1
values is 2
Can any one figure out why its happening ?
DEMO
var is hoisted, so to the interpreter, the first part's i actually looks more like this:
var funcs = [];
var i;
for (i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
// At this point, the for loop has finished; `i` has the value of 3 now:
for (var j = 0; j < 3; j++) {
// Each function retrieves the value of i and displays it
// and since `i` is 3, each function displays 3:
funcs[j]();
}
In your second sample (which is somewhat invalid JS because i gets declared twice), i gets reassigned to values 0, 1, and 2 while looping over the function that displays i.
Related
I have a code sample
for(var i = 0; i < 3; i++) {
setTimeout() {
console.log("i " + i)
}
}
This is printing "3" to the console three times. However, I want it to print "0, 1, 2" without using let.
So far, I have tried the following -
var funcs = [];
function createfunc(i) {
return function() {
console.log("i ", i)
}
}
for(var i = 0; i < 3; i++) {
setTimeout(() => {
funcs[i] = createfunc(i) // statement 1
}, 1000)
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
This is working fine if I use the statement 1 just as it is without putting it into the setTimeout function. However, it is throwing an error if I'm using the setTimeout function. Can someone let me know how can I make print 0, 1, 2 to the console using the setTimeout function ?
It happens because of closure, anyway you can use the below codes, then you will be getting the proper result that you want.
for(var i = 0; i < 3; i++) {
((i) => {
setTimeout(() => {
console.log("i " + i)
}, 0);
})(i);
}
Like was previously said here: JavaScript closure inside loops – simple practical example
The output for this code is always 3
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
}
// My value: 3
// My value: 3
// My value: 3
This happens is because there's no difference between the global i and the i inside the anonymous function of the loop. So that when funcs[i] gets called, its current value is already 3.
The classic way to solve this is via a closure.
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
}
By wrapping the function creation in the createfunc(i) function, you ensure that the value of "i" is bound to an unchanging value outside the function.
One way to solve this and that I don't get is through the creation of a let variable.
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
funcs[0]();
funcs[1]();
funcs[2]();
funcs[3]();
By doing so, I'd expect the variable let to be only accessible inside the loop, hence at the time I'm calling the function i is still undefined.
Since it works, it looks like JS is able to assign the proper variable i while still in the for loop and at the time I invoke the function it has the proper value i assigned.
What am I missing?
This is a closure.
The function is defined inside the loop, so it has access to all the variables that exist inside the loop, and it closes over them and continues to have access to them so long as the function exists.
In continuation to this question, I tried the following code that uses the same variable in both the loops and I get the desired result. My question is, WHY ?
So the initial code is:
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](); // this will not give desired output
}
The outputs this:
My value: 3
My value: 3
My value: 3
Whereas, the intended output is:
My value: 0
My value: 1
My value: 2
Now, if I use variable 'i' (a global variable) in the second loop as well, the code looks like:
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 ( i = 0; i < 3; i++) {
funcs[i](); // this gives desired output
}
I get the intended output,
My value: 0
My value: 1
My value: 2
WHY?
Now, if I use variable 'i' (a global variable) in the second loop as
well ... I get the intended output ... WHY?
Because when you do the first executing loop:
for (var j = 0; j < 3; j++) {
funcs[j]();
}
the loop constructing the array has finished and the global variable i has the value 3 and therefore console.log(i) results in logging 3 to the console for each iteration of the first executing loop. When you do the second executing loop:
for ( i = 0; i < 3; i++) {
funcs[i]();
}
you for each iteration of the loop assign a new value to the global variable i and it is this new value that the console.log(i) will log to the console: 0, 1, 2.
How do you achieve the desired result in a simple way?
You can use let to achieve the desired result in a simple way:
"use strict"; // use of let requires strict mode
var funcs = [];
for (let i = 0; i < 3; i++) { // just replace var with let here
funcs[i] = function() {
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Now the array will be constructed with functions having access to a local variable, which is assigned the value of i at the time of adding the function to the array.
Because the variable i is contaminated. In your snippets, The loops write to the same variable i and the functions reads it. Note that there is just one variable i in your code.
What you really need to get your intended result is called "module pattern", which wraps a function to keep a local copy of the variable in the loop:
for (var i=0; i<3; i++) {
(function (j) {
funcs[j] = function() { // and store them in funcs
console.log("My value: " + j); // each should log its value.
};
})(i);
}
You can read this article to get more information about "module pattern".
I gave up on this after doing some research. My question is below:
From the post, I understand the following code.
var funcs = {};
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
var item = "item" + i; // inside
console.log("item: " + item + ", i: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Result:
item: item3, i: 3
item: item3, i: 3
item: item3, i: 3
But if I change the move var item = "item" + i outside of function(), the result will be different. Why is this?
var funcs = {};
for (var i = 0; i < 3; i++) { // let's create 3 functions
var item = "item" + i; // outside
funcs[i] = function() { // and store them in funcs
console.log("item: " + item + ", i: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Result:
item: item2, i: 3
item: item2, i: 3
item: item2, i: 3
The key problem you're facing is that a closure capture variables by reference, not by value. For example:
var i = 0;
var f = function(){ i++; console.log(i); }
var g = function(){ i++; console.log(i); }
f(); g(); f(); g();
this will display 1, 2, 3, 4 because f and g are not capturing the value of the variable i, but the variable itself and therefore are both mutating the same variable.
A very common mistake is to create multiple closures in a loop forgetting that all of them will capture the same variable (the one used in the loop) and thus that when this loop variable is incremented all the already created closures will see it changing. For example in your code
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
var item = "item" + i; // inside
console.log("item: " + item + ", i: " + i);
};
}
item is a variable local to the function but i instead comes from the outside and it's shared between all of them. This can be seen for example with
i = 42;
funcs[0](); funcs[1](); funcs[2](); // Displays 42 three times
i = 99;
funcs[0](); funcs[1](); funcs[2](); // Now they all display 99
In the second case of your question things are just slightly different because in
for (var i = 0; i < 3; i++) {
var item = "item" + i;
funcs[i] = function() {
console.log("item: " + item + ", i: " + i);
};
}
also the item variable is now shared as it's defined outside the function and the closure captures it. In the display you see the last value that's assigned to it.
The key point to remember that in Javascript a variable is local to the function, not local to the {...} block (like it's in C++ for example) and thus the discriminating factor is if the variables are declared inside the function or outside the function, not if they're declared inside or outside the loop.
To overcome this common problem and have a separate index for each of the closures created in a loop often you will see code like
var f = (function(i){return function(){ ... };})(i);
where a function is created and immediately called to return another function.
The reason is that the only way to create a separate new variable is a function, leaving us with the pattern of creating a function that returns a function: the useful closure (the inner) in this case will capture a variable that is different for each of the iterations (because it's a parameter of the outer).
Changing your code to
for (var i = 0; i < 3; i++) {
funcs[i] = (function(i){return function() {
var item = "item" + i;
console.log("item: " + item + ", i: " + i);
};})(i);
}
Will work as expected (i.e. each function will now have its own i value).
EDIT
In current Javascript all the above applies to var variables (that are function scoped) but you can also use let variable that are instead block scoped.
This means the code can now be simplified and for example with:
lambdas = [];
for (let i=0; i<10; i++) {
let j = i*i; // Variable j is block scoped
lambdas.push(function(){ return j; });
}
console.log(lambdas[3]()); // ==> output 9
console.log(lambdas[5]()); // ==> output 25
(with var j = i*i; instead you'll get 81 for both)
In the first case the item variable doesn't exist until the function is run, at which point it is local to each function and gets set equal to "item" + i. And i is 3 because it is declared in the outer scope and 3 is what it was when the loop ended.
In the second case, the item variable exists in the functions' containing scope - the same scope as i - and it has been set to "item2" during the last iteration of the first loop. Inside that loop i had the values 0, 1, 2 - it only got set to 3 when the loop ended.
I'm not sure why I'm getting an error on the following snippet (adapted from JavaScript closure inside loops – simple practical example):
var funcs = {};
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = (function(n) { // and store them in funcs
console.log("My value: " + n); // each should log its value.
})(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
It seems like this should run ok; I know this is just something I don't fully get.
Here is the errror I get:
thx for any help
You need to return a function, rather than a result of the function. Try:
funcs[i] = (function(n) {
return function() {
console.log("My value: " + n);
}
})(i);
Example:
> var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = (function(n) {
return function() {console.log("My value: " + n);}
})(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
My value: 0
My value: 1
My value: 2