This question already has answers here:
Why let and var bindings behave differently using setTimeout function? [duplicate]
(2 answers)
Closed 5 years ago.
I understand that let has block scope and var has functional scope. But I do not understand in this case, how using let will solve the problem
const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(arr[i])
}, 1000);
} // Prints undefined 5 times
const arr = [1,2,3,4];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(arr[i])
}, 1000);
} // Prints all the values correctly
This is all related to the scope of the variable. Let's try to wrap both the pieces into functions, and observe the output:
function test() {
// `i` will be declared here, making it a non-for-loop scoped variable
const arr = [1, 2, 3, 4];
for (var i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(arr[i])
}, 1000);
} // Prints undefined 5 times
}
test();
So in the first case, i will be hoisted, and because of the asynchronous nature of setTimeout, i will immediately become 4 as the loop ends without waiting. This will make arr[i] to point to an undefined element in the array.
In the second case, i is not hoisted, and has scoped access to each iteration of the loop, making i accurately available to console.log statement. Thus the results are as per the expectations:
function test() {
const arr = [1, 2, 3, 4];
for (let i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log(arr[i])
}, 1000);
} // Prints all the values correctly
}
test();
First of all, the output will be four times and not five times(as mentioned in your comment).
I pasted your code in Babel REPL and this is what I got,
"use strict";
var arr = [1, 2, 3, 4];
var _loop = function _loop(i) {
setTimeout(function () {
console.log(arr[i]);
}, 1000);
};
for (var i = 0; i < arr.length; i++) {
_loop(i);
}
Do you see how let works internally now? :-)
You can still use var for setTimeout. You can use an immediately-invoked function expression (IIFE) to create a closure around setTimeout such that the value of i is recognised by the setTimeout function.
const arr = [1,2,3,4];
for (var i = 0; i < arr.length; i++) {
(function(i){
setTimeout(function() {
console.log(arr[i])
}, 1000)})(i);
}
Related
Why does javascript closure work differently in these examples?
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 1000);
}
// Output: 0, 1, 2
let i = 0;
for (i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 1000);
}
// Output: 3, 3, 3
In the first code example, you have three different i variables and in each iteration of the loop, callback function of setTimeout closes over a different copy of i variable.
In the second code example, there is only one i variable and in each iteration of the loop, callback function of setTimeout closes over the same i variable.
There is only one i variable in the second code example because you have declared it outside the loop; as a result, each iteration of the loop sees the same variable i.
In order to get the same output in the second code example, change it as shown below:
let i = 0;
for (i = 0; i < 3; i++) {
let j = i; // save a copy of the current value of 'i'
setTimeout(() => {
console.log(j); // closure over a different 'j' variable in each iteration
}, 1000);
}
This question already has answers here:
Let vs. var in a for loop
(1 answer)
How do JavaScript closures work?
(86 answers)
Explanation of `let` and block scoping with for loops
(5 answers)
I am confused with javascript let and var in for loop? [duplicate]
(1 answer)
Closed 1 year ago.
I do understand the javascript closure as I think and I did a lot of small programs with it but in this code what I didn't understand is When I call the functions in the array why they are printing out different values of "i" aren't they suppose to refer to the same "i"?
function makeFunctionArray() {
const arr = []
//let i = 0
for (let i = 0; i < 5; i++) {
arr.push(function () { console.log(i) })
}
//console.log(i)
return arr
}
let functionarr = makeFunctionArray()
functionarr[0]() //print out 0
functionarr[2]() //print out 2
functionarr[1]() //print out 1
When you do
for (let i = 0; i < 5; i++) {
arr.push(function () { console.log(i) })
}
The arr would have the values
[console.log(0),
console.log(1),
console.log(2),
console.log(3),
console.log(4)];
So when you return arr and then you call functionarr like this
functionarr[0]() //print out 0
functionarr[2]() //print out 2
functionarr[1]() //print out 1
It would return:
console.log(0);
console.log(1);
console.log(2);
REMEMBER : it is an scope issue. If you use var, when you call the i variable into the array function, it would return the last i value. Thats the reason of why it returns always 5. If you use let, it would return the local i value, so it would save [0, 1, 2, 3, 4].
Using let
const arr = [];
for (let i = 0; i < 5; i++) {
arr.push(
function () {
console.log(i); //i value at the moment that you create function would be saved
}
);
}
arr[0](); //print 0
Using var
const arr = [];
for (var i = 0; i < 5; i++) {
arr.push(
function () {
console.log(i); //final i value would be saved (current i value)
}
);
}
arr[0](); //print 5
Quoting from the book JavaScript: The Definitive Guide
Unlike variables declared with let, it is legal to declare the same variable multiple times with var. And because var variables have function scope instead of block scope, it is actually common to do this kind of redeclaration. The variable i is frequently used for integer values, and especially as the index variable of for loops. In a function with multiple for loops, it is typical for each one to begin for(var i = 0; .... Because var does not scope these variables to the loop body, each of these loops is (harmlessly) re-declaring and re-initializing the same variable.
I am unable to understand what is meant in this paragraph. First I assumed the following would not have worked with second loop not iterating, because i would be function scoped:
(function foo() {
for (let i = 0; i < 2; i++) {
console.log(i);
}
for (let i = 0; i < 2; i++) {
console.log(i);
}
})();
but it prints
0
1
0
1
Then I assumed this would have printed 0 0 1 1 0 1, which is also not the case:
(function foo() {
for (var i = 0; i < 2; i++) {
console.log(i);
for (var i = 0; i < 2; i++) {
console.log(i);
}
}
})();
Can someone help me understand what is meant by
in a function of multiple for loops, var can be used harmlessly in each loop
and how it is different to let?
To me it looks like the opposite it true (which is also very confusing) where let can be used harmlessly in functions with multiple loops:
(function foo() {
for (let i = 0; i < 2; i++) {
console.log(i);
for (let i = 0; i < 2; i++) {
console.log(i);
}
}
})();
prints:
0
0
1
1
0
1
let variables are forbidden to be re-declared in the same scope, like the following:
let foo = 10;
let foo = 20;
var variables can be, though:
var foo = 10;
var foo = 20;
console.log('ok');
In your first snippet, variables declared with let in the header of a for loop only exist inside the loop; they're block scoped, and aren't scoped to the outer block or function:
(function foo() {
for (let i = 0; i < 2; i++) {
// console.log(i);
}
for (let i = 0; i < 2; i++) {
// console.log(i);
}
// doesn't exist out here:
console.log(i);
})();
The let is above create is in two different scopes, which is why it's not forbidden. Similarly:
(function foo() {
{
let i = 10;
}
{
let i = 20;
}
console.log('OK up until here');
// doesn't exist out here:
console.log(i);
})();
vars, in contrast, have function scope, not block scope - with your var version of the loop, since there's only one function, this:
(function foo() {
for (var i = 0; i < 2; i++) {
console.log(i);
for (var i = 0; i < 2; i++) {
console.log(i);
}
}
})();
is equivalent to
(function foo() {
var i; // the `i` variable exists at this level
for (i = 0; i < 2; i++) {
console.log(i);
for (i = 0; i < 2; i++) {
console.log(i);
}
}
})();
Then I assumed this would have printed 0 0 1 1 0 1, which is also not the case:
i gets initialized to zero at the beginning of each loop.
The first loop begins once, at the start of the function.
The second loop begins just after logging i, on each iteration of the outer loop.
(function foo() {
for (var i = 0; i < 2; i++) {
console.log(i);
for (var i = 0; i < 2; i++) {
console.log(i);
}
}
})();
outer loop initialization: 0 is assigned to i
0 gets logged
inner loop initialization: 0 is assigned to i
0 gets logged
inner loop increments i to 1 and starts again
inner loop: 1 gets logged
inner loop increments i to 2 and breaks, since i < 2 is no longer fulfilled
outer loop increments i to 3 and breaks, since i < 2 is no longer fulfilled
So you get 0 0 1 logged.
What the article probably means by
in a function of multiple for loops, var can be used harmlessly in each loop
is that it can work if both loops are on the same level of the function, eg
for (...) {
}
for (...) {
}
It definitely won't work with nested loops, if both loops use the same variable, because there's only ever a single i variable, rather than one for each loop.
where let can be used harmlessly in functions with multiple loops:
For nested loops, yes, since variables declared with let are unique to each loop, rather than being shared across the whole containing function.
This question already has answers here:
What is the difference between "let" and "var"?
(39 answers)
Closed 5 years ago.
Although i understand that let allows you to declare variables that are limited in scope to the block, I encountered a strange difference between let and var while using it with javascript closures. Here is my example:
With let:
function buildFunction() {
var arr = [];
for(var i = 0; i < 3; i++) {
let j = i; //Using let to assign j
arr.push(
function(){
console.log(j);
}
)
}
return arr;
}
var fs = buildFunction();
fs[0]();
fs[1]();
fs[2]();
The above code snippet outputs:
0
1
2
With var:
function buildFunction() {
var arr = [];
for(var i = 0; i < 3; i++) {
var j = i; //Using var to assign j
arr.push(
function(){
console.log(j);
}
)
}
return arr;
}
var fs = buildFunction();
fs[0]();
fs[1]();
fs[2]();
The above code snippet outputs the following:
2
2
2
My question is:
If U am using var inside a block and assigning it a value during execution, shouldn't it work exactly like let and store different copies of j in memory ?
Does javascript handle let and var differently within a closure ?
Any clarifications on this would be highly appreciated.
var scopes to the function; let scopes to a block of code.
In your example j is scoped to the function buildFunction() when you use var. This means your using the 'same' j in every function. When your for loop runs j is set to 0, then 1, then 2. When you then run the console logs your referencing the j that was set to 2, so you get 2 2 2.
When you use let, you scope j to that iteration of the for loop. This means every iteration has a 'different' j, and the console logs print what you expect them to print.
If you were using ES5 and needed to use var, you could replicate the let effect (have it print 0 1 2), by wrapping your original anonymous function with a self-executing function and passed j in as an argument. This would create a new scope for j in the self-executing function whose value is the value of j in the current iteration.
function buildFunction() {
var arr = [];
for(var i = 0; i < 3; i++) {
var j = i; //Using var to assign j
arr.push(
//self executing function
(function(j) { //j here is scoped to the self executing function and has the value of j when it was called in the loop. Any changes to j here will not affect the j scope outside this function, and any changes to j outside this function will not affect the j scoped inside this function.
//original function
return function() {
console.log(j);
}
})(j) //call with the value of j in this iteration
)
}
return arr;
}
var fs = buildFunction();
fs[0]();
fs[1]();
fs[2]();
This question already has answers here:
How do JavaScript closures work?
(86 answers)
Closed 7 years ago.
I have code that looks like this:
var a = [];
for(var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
}
}
Unfortunately, it seems that i is being passed by reference, so all the functions in a output 10. How do I make it so that each function outputs the value that i had when it was created? I.e. a[0]() gives 0, a[1]() gives 1, etc.
EDIT: to clarify, I do not want a to store the values 0-9. I want a to store functions that return the values 0-9.
You need to invoke a function (to create a closure that captures your value) which returns a function (the one you want to end up with). Something like this:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = (function(value) {
return function() {
console.log(value);
}
})(i);
}
var a = [];
for(var i = 0; i < 10; i++) {
a[i] = (function(j) {
return function () {
console.log(j);
}
})(i);
}
A better performing version -
function makeFunction(i) {
return function () {
console.log(i);
}
}
var a = [];
for(var i = 0; i < 10; i++) {
a[i] = makeFunction(i);
}
JSFiddle Demo.