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.
Related
i think i sort of understand the concept of hoisting when var is used to initialize i in a for loop and the concept of function closure. but why does the following code do not display an error as the final value of i is 4 and arr[3] is the highest index for arr?
why isn't the i in arr[i] linked to the i declared in the for-loop: var i = 0 ? the point of this question is to understand the concept instead of getting the solution
var arr = [1, 2, 3, 4];
for (var i = 0; i<arr.length; i++){
console.log(arr[i])
}
compared to this, the output shows the final value of i (ie 3) three times. i know that let should be used here
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]();
}
In the first snippet the highest value is 4 but the max array index is 3 And since your loop is using array index length as it's max value, it doesn't go beyond the max index.
To understand the second example you'll need understand the difference between var and let.
The most basic explanation is var scopped to it's immediate function body (in your case outside of for loop, the same scope as funcs array)
while let scopped inside the enclosure (inside the for loop) and it basically creates create a new variable with each iteration of the loop, while var reuses the same variable.
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".
The following snippet is from "Example 5" from this thread on JS and closure. Alternatively, this other thread sort of gets at what I'm curious about.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
The thread this snippet is from says the output is: "item3 undefined"x3. I understand why "item3" is printed all three times, but I don't understand why list[i] is undefined.
My Question: since list is a parameter of buildList, does that mean it's not a part of the closure of the anonymous function within result.push? If there were a variable within buildList (otherList) which was set to list (and the anonymous function used otherList[i]), would otherList[i] be in the closure? (Therefore instead of "item3 undefined"x3, the output would be "item3 3"x3).
It has three elements, so maximum valid index is 2. So it is not printing item3 undefined it is item2 undefined.
Reason :
Since you know that the function is printing item-2 string each time because, the function was bounded to the reference, not the value. (This can be avoided using Immediate invoking functions).
But the loop stops/halts, when i cross the i < array.length, So the last updated value of i is now 3 and array[1, 2, 3] at index 3 is undefined. Hence you are seeing undefined value.
Verification:
add console.log as shown below
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {
console.log(i); //Prints 3 for all 3 invocation.
alert(item + ' ' + list[i])
} );
}
So i already learnt a lot about closures here on SO.
But i still found one examples i can't explain to myself:
function buildList(list){
var result = [];
for(var i = 0; i < list.length; i++) {
var item = "item: " + list[i] + " i's value: " + i; //i has the value of 2 here, but why?
result.push( function(){document.write(item + ", list[i]:" + list[i] + ", i's value in the anonymous function:" + i + "<br>")});
} //i = 3 here
return result;
}
function testList(){
var testTheList = buildList([1,2,3]);
for (var j = 0; j < testTheList.length; j++) {
testTheList[j]();
}
}
testList();
As i would expect of the closure, i should be 3 when i execute the testList().
But the result is:
item: 3 i's value: 2, list[i]:undefined, i's value in the anonymous function:3
item: 3 i's value: 2, list[i]:undefined, i's value in the anonymous function:3
item: 3 i's value: 2, list[i]:undefined, i's value in the anonymous function:3
Why does is i for the var item => 2 and i inside the anonymous function => 3 ? As i read, closures create new execution environments, but shouldnt i be the same value for the closure?
EDIT
This is not a duplicate of JavaScript closure inside loops – simple practical example, i don't want to know, how to create a new execution environment.
I want to know why the value of the same variable i (of the the loop) differs in the same scope?
When you adding your new function to result list it's holding reference to item variable and i counter. During the loop inside buildList function you aren't creating few item variables, but you are overwriting existing one, at the end of buildList function execution values of item and i variables looks like this:
3
"item: 3 i's value: 2"
And list[i] is undefined because your list length is 3, you don't have list[3] item. So when you are calling your anonymous function from loop in testList it's retrun you exact same values as item and i variables keep. Also create anonymous functions in loops it's not the best practice, I would suggest you to modify your buildList function like this:
function buildList(list) {
var index = 0;
return function () {
if (index >= list.length) return
var item = "item: " + list[index] + " i's value: " + index;
document.body.innerHTML += [
item, ", list[i]:", list[index],
", i's value in the anonymous function:",
index, "<br>"
].join('')
index++
}
}
function testList() {
var list = [1, 2, 3];
var testTheList = buildList(list);
document.body.innerHTML = '';
for (var j = 0; j < list.length; j++) {
testTheList();
}
}
testList();