consider the following code
var fs = [];
for(var i=0;i<10;i++){
fs.push(i => console.log(i));
}
fs.forEach( f => f());
If the function is changed to:
for(let i=0;i<10;i++){
fs.push(function(){
console.log(i)
});
}
It will print the 1,2,3,4,5,6,7,8,9 expected output.
I am not understanding why. Can someone help.
let and var makes no difference for your code with regard to why you're getting 10x undefined.
What does make a difference is that your arrow function is defined taking a parameter i (overwriting the outer index i) whereas in your 2nd example it is not.
const fs = [];
for (var i = 0; i < 10; i++) {
fs.push(() => console.log(i));
}
fs.forEach(f => f());
If you add that parameter to your second example, you also get 10x undefined.
const fs = [];
for (let i = 0; i < 10; i++) {
fs.push(function(i) {
console.log(i)
});
}
fs.forEach(f => f());
The difference between var and let in your code is more subtle:
const fs = [];
for (var i = 0; i < 10; i++) {
fs.push(() => console.log(i));
}
fs.forEach(f => f());
const fs = [];
for (let i = 0; i < 10; i++) {
fs.push(() => console.log(i));
}
fs.forEach(f => f());
With let every function in the array has a locally scoped closure i, whereas with var at execution time i is 10 (because there is only a single variable i that is closured in each function).
There are 2 different issues which need to be covered in some detail. Lets focus on the first case where the main issue is that you define an ES6 arrow function and call it later with no parameters:
i => console.log(i) when converted to ES5 anonymous function notation is:
function(i){ console.log(i) }
So in i => console.log(i) what you have is a short hand ES6 definition of an anonymous function (also known as ES6 arrow function notation) which accepts a parameter i.
The end result is that console.log(i) is trying to print an i which is undefined since it is not passed to that arrow function at execution time.
You re pushing a function definition which later you are executing without passing into it a parameter which it needs to actually output it in the console.
var fs = [];
for(var i=0; i<10; i++){
// You are pushing to the array the below anonymous function definition
fs.push(
// You are creating an anonymous function which accepts i and console.logs it
i => console.log(i)
);
}
fs.forEach(
// You are calling the "pushed" above function definition with NO parameter i
f => f()
);
Now lets explore why and how the 2nd code example works and how var/let play a big role on console output:
let fs = []
// You define i in the for-loop block score
for(var i=0; i<10; i++){
fs.push(
// You push an annonymous function definition wich has a reference of i in the same block scope
// However i is already here 10 since the loop is already done and it takes the last value of i
function(){ console.log(i) }
);
}
fs.forEach(
// You call f which has a reference inside to the i from the for loop
f => f()
);
So in this case i when you use var i the i since it does not retain its lexical block scope ends up being updated to 10 before the console.log gets called.
Lets try this now with let:
let fs = []
// You define i via LET in the for-loop block score
for(let i=0; i<10; i++){
fs.push(
// You push an annonymous function definition wich has a reference of i in the same block scope
// i retains its lexical scope in for loops
function(){ console.log(i) }
);
}
fs.forEach(
// You call f which has a reference inside to the i from the for loop
f => f()
);
So little bit more on let:
let allows you to declare variables that are limited in scope to the
block, statement, or expression on which it is used. This is unlike
the var keyword, which defines a variable globally, or locally to an
entire function regardless of block scope.
In your first example, the i in fs.push(i => console.log(i)); is the parameter of the arrow function. this is effectively equivalent to fs.push(function(i){ return console.log(i); });
This parameter has the same name as the iterator of the loop. In the scope of the function you are pushing, the parameter i takes priority over the outside variable. If you want to capture the iterator you should not name it the same as the parameter, like so:
fs.push(() => console.log(i));
Now you will see there is a difference between var and let in this situation. Your output will be 10,10,10,10,10,10,10,10,10,10. This is because of the scoping difference between var and let. When declared with var, the loop iterator will be the same object throughout the duration of the loop. All of the functions are capturing the same object. The final value after the loop ends (i++ happens and then the conditional check happens) is 10, so they all print 10. With let, the iterator is a new object each iteration of the loop, so each function captures a new object which holds the current count of the loop.
Related
To my understanding, if the loop variable of a for loop is defined with var, then any change on that variable is applied globally. for example:
var printNumTwo;
for (var i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
In the above code, 3 will be printed into the console. Because in the last iteration the variable i will equal to 3. Therefore when printNumTwo is called, the update i will be returned.
However if I use let this is not the case and the following behavior happens:
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
The above code will print 2.
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
i = 3;
}
}
console.log(printNumTwo());
however, the above code prints 3.
What is the reason for this?
source: The first two code blocks are from here.
In the first example, var scopes i to the function, so at the end of the loop i++ changes i from 2 to 3 and the loop stops. The only i is now 3 and printNumTwo returns i which is 3.
In the second example let scopes i to the block, so the function assigned to printNumTwo closes over it. The last time the body of the loop runs, i is 2 and printNumTwo returns i which is 2. (A new i is created with the value 3 but no function is created that uses it).
In the third example, let scopes i to the block and the same thing happens except you have i = 3; which changes every i to 3. So the last function that is created (as well as the previous ones which are overwritten) returns i which is 3.
however, the above code prints 3. What is the reason for this?
Because you assign 3 to the i variable that printNumTwo closes over. It doesn't matter that the assignment happens after printNumTwo is created, only that it is the variable that printNumTwo is using.
The difference between var and let in for loops is that a new variable is created for the body of the loop on each loop iteration with let. But you're assigning to that variable within the loop body, so the function closing over (printNumTwo) it sees that value later when you call it.
It's exactly like this:
function create(i) {
// This function closes over `i`
const fn = function() {
return i;
};
// This modifies the `i` it closes over
++i;
return fn;
}
const printValue1 = create(1);
console.log(printValue1()); // 2 (1 + 1)
const printValue27 = create(27);
console.log(printValue27()); // 28 (27 + 1)
In response to a comment on the question, you've said:
same thing also happens in the second code block (i++ in the update section in for loop ) but the answer is 2
Ah! Now I see why there's a misunderstanding. You're right that there's an update to i — but it's not the i that printNumTwo closes over.
The update section of the for is performed on the new variable for the next iteration at the start of the next iteration, not on the one for the iteration that just finished at the end of the iteration. It does this:
Create a new iteration variable (let's call it iNew)
Assign the value of the old iteration variable (let's call it iOld) to the new variable (iNew = iOld)
Run the update code using iNew (iNew++)
That's why i (the old one) remains 2; it's the new one that becomes 3.
Scope of var is the function and scope of let is the block.
So when assigning value to the printNumTwo function, the value of i is still 2. doesn't matter what happens to i later.
The behavior of var and let you can understand from this example.
for(var i=0; i<3; i++){
setTimeout(() => console.log('var', i))
}
for(let i=0; i < 3; i++){
setTimeout(() => console.log('let',i))
}
This question already has answers here:
Explanation of `let` and block scoping with for loops
(5 answers)
Closed 3 years ago.
Consider the following code snippet:
for (let i = 0; i < 5; i++) {
i+=1;
setTimeout(() => console.log(i), 100);
}
console.log('after for loop');
If let i were creating a new block scope variable with each iteration, I would expect it to output:
1
2
3
4
5
because, being a new block scope variable, i+=1; would only make changes to my local copy of i. Also, if i was a new block scope variable, that would explain why the setTimeout callback doesn't log "6" 3 times (as it does if let i is changed to var i).
In case it helps, here is what I'm imagining it would be doing under the hood if it were creating a new block scope variable for each iteration:
for (let I = 0; I < 5; I++) {
let i = I;
i+=1;
setTimeout(() => console.log(i), 100);
}
console.log('after for loop');
However, the top snippet actually outputs:
1
3
5
which would make sense if i were shared between all iterations except if i were shared between all iterations why would the setTimeout callback not print the same number 3 times?
In short my question is:
Why, in the top snippet, is i+=1; both updating the loop variable as if i in i+=1; is not a local copy for each iteration and also behaving in the setTimeout callback as if i is a local copy for each iteration.
When the variable declared in the for loop declaration gets reassigned within the loop body, that reassignment will persist for the next iteration. It's a lot clearer if you look at how Babel transpiles it:
for (let i = 0; i < 5; i++) {
i+=1;
setTimeout(() => console.log(i), 100);
}
console.log('after for loop');
results in
"use strict";
var _loop = function _loop(_i) {
_i += 1; // <---------------
setTimeout(function() {
return console.log(_i);
}, 100);
i = _i; // <---------------
};
for (var i = 0; i < 5; i++) {
_loop(i);
}
console.log("after for loop");
If you change or log i outside of the synchronous execution of the for loop body, it will (essentially) refer to the _i above, acting like a completely independent block-scoped variable. But if you change i inside the synchronous for loop body, the next iteration will start with the changed i.
I have been working through this article on closures: Understand Javascript Closures with Ease
The final example deals with a closure inside a for loop.
I understand why an IIFE is used to capture the current value of 'i' as 'j'. What I don't understand about this example is why there is a 2nd, inner IIFE wrapped around the return statement. (My comment is in caps in the code below).
The code seems to work just the same without the inner IIFE. See CodePen here.
Is this inner function required for some reason, or is this just an oversight by the author?
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function (j) { // the j parametric variable is the i passed in on invocation of this IIFE
return function () { //<--WHY DOES THIS INNER FUNCTION NEED TO BE HERE?
return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array
} () // BY adding () at the end of this function, we are executing it immediately and returning just the value of uniqueID + j, instead of returning a function.
} (i); // immediately invoke the function passing the i variable as a parameter
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0},{name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100
var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101
var willisID = createIdForActionCelebs[2];
console.log(willisID.id); //102
The inner function, as you observed, has no practical effect. It doesn't make sense to use it.
It appears to be a holdover from the previous example in the article where a function was returned instead of a number.
Can anyone explain why document.write part always outputs 10?
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
for (var i = 0; i < funcs.length; i++) {
document.write(funcs[i]() + "<br />");
}
I think the answer to this question is more thorough than the duplicate question, therefore worth keeping this post.
Sure.
In your for loop, you reference i. What you expect to be happening is that each closure gets a snapshot of i at the time the function is created, therefore in the first function it would return 0, then 1, etc.
What's really happening is that each closure is getting a reference to the external variable i, which keeps updating as you update i in the for loop.
So, the first time through the loop, you get a function that returns i, which at this point is 0. The next time you get two functions which return i, which is now 1, etc.
At the end of the loop, i==10, and each function returns i, so they all return 10.
UPDATE TO ADDRESS QUESTION IN COMMENT:
It's a little confusing since you use i in two different contexts. I'll make a very slight change to your code to help illustrate what's going on:
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
// NOTE: I changed this `i` to `unrelated_variable`
for (var unrelated_variable = 0; unrelated_variable < funcs.length; unrelated_variable++) {
document.write(funcs[unrelated_variable]() + "<br />");
}
... the functions that you create in your creatFunctions() function all return i. Specifically, they return the i that you create in the for loop.
The other variable, which I've renamed to unrelated_variable, has no impact on the value returned from your closure.
result[i] = function () {
return i;
}
... is not the same thing as result[2] = 2. It means result[2] = the_current_value_of_i
Because you reference i as a variable, which, when the function executes, has the value 10. Instead, you could create a copy of i by wrapping in in a function:
(function (i) {
result[i] = function () {
return i;
}
}(i));
The i that you are returning inside the inner function - is the i that was declared inside the for(var i=0...) therefor - it is the same i for all of the functions in result
and by the time you are calling the functions its value is 10 (after the loop ends)
to accomplish what you wanted you should declare another variable inside the scope of the anonymous function
Because reference to "i" is boxed (enclosed) in your closure that you defined.
result[i] = function () {
return i;
}
And it just keeps reference to the "i" var which keeps changing with each iteration.
UPDATE
This is related to the this keyword function scope which defines variable context.
Hence, in JavaScript, there's no such thing as block scope (for, if else, while, ...).
With that in mind, if you assign var funcs to a function definitioncall, the function body (this) will be bound to the global scope var i inside that function will reach the end of the for loop and stays there in memory. In this case i is 10.
Unfortunately for you, since function scope puts every variable to the top, var i will be global as well.
Although I was completely wrong at first, I stand corrected and took the liberty to create the output of your script in this demo.
I was going through a javascript book and have encountered the following code on closure
function constFunc() {
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = function () {
return i;
}
}
return funcs;
}
var f = constFunc();
f[5]();
The output was not 5.
The explanation given was "nested functions do not make private copies of the scope".
i would be grateful if could someone explain the above statement.
When you call constFunc the loop runs and assigns a function to each element of the funcs array. Those functions each return the value of i, which changes on each iteration of the loop from 0 up to 9.
That means that once constFunc has finished executing i will have the value of 9. When you call one of the functions in the array it returns the value of i which as we've just seen is now 9. The value of i is not captured on each iteration of the loop.
You can force it to be captured via a closure in the loop:
for (var i = 0; i < 10; i++) {
(function (i) {
funcs[i] = function () {
return i;
};
}(i));
}
In this case you are effectively creating a new variable with the same value as i on every iteration of the loop. You can see that in action here.
By passing i into a function you end up copying the value of it. So on the first iteration i has the value 0. We pass that value into the new function (the argument i is a different variable to the i declared in the loop initialiser) and then return that value from the array function. This way, each array function is returning a different i, instead of each returning the same i.
WHy did you expect the output of 5? RIght, because at the time the function was created, i was 5. Now, as we can see, the output is not 5 (but, if I didn't miss anything, 9). Now why is that?
The iinside the closure is not evaluated when the function is created, but when it runs. Now this is where the statement you qoted comes into play: there is only ONE i- the one created inside constFunc. All closures work on that one. After constFunc si done working, that i has, obviously, the value of 9 (becaus that's where the loop stops). SO all closures now output 9.
A simpler example:
function outer () {
var i = 0;
function inner () {
return i;
}
i = 99;
}
outer();
inner(); // <- return 99, not 0.
There is one variable called i.
There are 10 functions that return i.
Each one of those functions returns the current value of the single i variable.
Here in this loop:
for(var i=0;i<10;i++)
{
funcs[i]=function(){return i;}
}
due to closure, inside function you are accessing parent level variable of i, which after complete loop will be 10. So after loop when you call these function it will return you value of i which is 10.
You can solve it by passing variable as parameter, as parameter create the new copy of variable which maintain its state even on completion of loop.
var myFunctions= [];
function createMyFunction(i) {
return function() {
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
myFunctions[i] = createMyFunction(i);
}