How does the let keyword work in this code? [duplicate] - javascript

I understand that let prevents duplicate declarations which is nice.
let x;
let x; // error!
Variables declared with let can also be used in closures which can be expected
let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms
What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:
// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?
I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.

Is this just syntactic sugar for ES6?
No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
CreatePerIterationEnvironment.
How is this working?
If you use that let keyword in the for statement, it will check what names it does bind and then
create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)
copy the values from all variables with those names from one to the next environment
Your loop statement for (var i = 0; i < 10; i++) process.nextTick(_ => console.log(i)); desugars to a simple
// omitting braces when they don't introduce a block
var i;
i = 0;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
…
while for (let i = 0; i < 10; i++) process.nextTick(_ => console.log(i)); does "desugar" to the much more complicated
// using braces to explicitly denote block scopes,
// using indentation for control flow
{ let i;
i = 0;
__status = {i};
}
{ let {i} = __status;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
…

I found this explanation from Exploring ES6 book the best:
var-declaring a variable in the head of a for loop creates a single
binding (storage space) for that variable:
const arr = [];
for (var i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [3,3,3]
Every i in the bodies of the three arrow functions refers to the same
binding, which is why they all return the same value.
If you let-declare a variable, a new binding is created for each loop
iteration:
const arr = [];
for (let i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]
This time, each i refers to the binding of one specific iteration and
preserves the value that was current at that time. Therefore, each
arrow function returns a different value.

let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.
The MDN explanation supports this as well, stating that:
It works by binding zero or more variables in the lexical scope of a single block of code
suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.
In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.
Adapting your example to run in the browser:
// prints '10' 10 times
for (var i = 0; i < 10; i++) {
setTimeout(_ => console.log('var', i), 0);
}
// prints '0' through '9'
for (let i = 0; i < 10; i++) {
setTimeout(_ => console.log('let', i), 0);
}
certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:
for (var i = 0; i < 10; i++) {
setTimeout(function(_) {
return console.log(i);
}, 0);
}
var _loop = function(_i) {
setTimeout(function(_) {
return console.log(_i);
}, 0);
};
// prints '0' through '9'
for (var _i = 0; _i < 10; _i++) {
_loop(_i);
}
Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.

Recently I got confused about this problem too. According to the above answers, here is my understanding:
for (let i=0;i<n;i++)
{
//loop code
}
is equivalent to
// initial
{
let i=0
}
// loop
{
// Sugar: For-Let help you to redefine i for binding it into current block scope
let i=__i_value_from_last_loop__
if (i<=n){
//loop code
}
i++
}

Let us see “let” and “var” with the setTimeout majorly asked in the interview.
(function timer() {
for (var i=0; i<=2; i++)
{ setTimeout(function clog() {console.log(i)}, i*1000); }
})();
(function timer() {
for (let i=0; i<=2; i++)
{ setTimeout(function clog() {console.log(i)}, i*1000); }
})();
Let's see in detail how this code executes in the javascript compiler.
The answer for “var” is “222” due to functional scope and for “let” is “012” because it is block scope.
Now let us look at what it looks like in detail when it Compiles for "var". (It's a little hard to explain over the code than in audio or video but I am trying my best to give you.)
var i = 0;
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 1
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 2
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 3
After the code is executed finally it will print all the console.log where the value of "i" is 6. So the final output is: 222
In "let i" will be declared in every scope.
The import point to be noted here is "i" will get the value from the previous scope and not from the declaration. (Below code is just an example of how it looks like in the compiler and trying it wont work)
{
//Scope 1
{
let i;
i= 0;
if(i<=2) {
setTimeout(function clog() {console.log(i)};);
}
i++; // Here "i" will be increated to 1
}
//Scope 2
// Second Interation run
{
let i;
i=0;
// Even “i” is declared here i= 0 but it will take the value from the previous scope
// Here "i" take the value from the previous scope as 1
if(i<=2) {
setTimeout(function clog() {console.log(i)}; );
}
i++; // Here “i” will be increased to 2
}
//Scope 3
// Second Interation run
{
let i;
i=0;
// Here "i" take the value from the previous scope as 2
if(i<=2) {
setTimeout(function clog() {console.log(i)}; );
}
i++; // Here "i" will be increated to 3
}
}
So, it will print "012" value as per the block scope.

Related

Javascript closures with let variable

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);
}

Using var in functions with multiple loops?

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.

Blank Local Variable Declaration - JS

I have exactly the same code below except the top is from the HeadFirst JS book where they declare output as a blank variable then assign it a string. Second is me messing around and seeing the code still works without first declaring output as a blank variable. What's the use for declaring output; blank vs just skipping that part.
const printAndGetHighScore = function (score) {
let highscore = 0;
let output;
for (i = 0; i < scores.length; i++) {
output = `Bubble solution #${i} score: ${scores[i]}`;
console.log(output);
if (scores[i] > highscore){
highscore = scores[i]
}
}
return highscore;
};
const printAndGetHighScore = function (scores) {
let highscore = 0
for (let i = 0; i < scores.length; i++) {
let output = `Bubble Solution #${i} score: ${scores[i]}`
if(scores[i] > highscore){
highscore = scores[i]
}
}
return highscore;
}
console.log(`Bubbles test: ${scores.length}`);
console.log(`Highest bubble score ${printAndGetHighScore(scores)}`)
In this case, there is no difference. Since output is only referenced once, and it's done synchronously inside the loop right after output gets assigned to, it doesn't matter where the variable is declared - from the perspective of getting the code working.
One could have even declared output outside, or highscore outside:
let output;
let highscore;
const printAndGetHighScore = function (score) {
highscore = 0;
for (i = 0; i < scores.length; i++) {
output = `Bubble solution #${i} score: ${scores[i]}`;
console.log(output);
if (scores[i] > highscore){
highscore = scores[i]
}
}
return highscore;
};
This would work too.
A situation where declaring variables outside a loop instead of inside a loop wouldn't work would be if the variables were referencable later, such as in functions:
const fns = [];
let i = 0;
for (; i < 3; i++) {
fns.push(() => console.log(i));
}
for (const fn of fns) fn();
In the above example, since there's only one binding for i created, the created functions all reference that single i, which is 3 after the loop finishes. Declaring a variable inside the loop would allow all functions to have separate bindings:
const fns = [];
for (let i = 0; i < 3; i++) {
const inner = i; // not strictly necesary, but makes the scoping easier to understand
fns.push(() => console.log(inner));
}
for (const fn of fns) fn();
But there's another reason to prefer declaring variables inside a loop (like your second example) instead of outside: code maintainability. The narrower a scope a variable has, the easier it is to reason about it and the code around it.

Can for-loops counter be const declaration in ES2015?

First, let can be used on loop counter declaration. This is also described in MDN.
for(let i = 0; i < 2; ++i) {
setTimeout(function(){
document.write(i + "<br>");
}, 0);
}
result:
0
1
Since let is used, a value of i can be changed in inside of for block.
for(let i = 0; i < 2; ++i) {
setTimeout(function(){
document.write(i + "<br>");
}, 0);
i = 123; // I want to block this assignment!!
}
So I considered using const instead of let.
for(const i = 0; i < 2; ++i) { // throws Assignment to constant variable
setTimeout(function(){
document.write(i + "<br>");
}, 0);
i = 123; // not here...
}
However, this code throws Assignment to constant variable. (in Chrome 53.0.2773.0).
I expected this loops twice like first let example, but make counter i writable in internal for-loop iteration only.
I think this is allowed because ECMA-262 §13.7.4.7 seems to mention this situation as If isConst is true, ..., but I cannot find this usage (or implementation status) in MDN and other sites. Kangax's table contains tests for let bindings, but not for const bindings.
I tested it (in Firefox) and the const binding works according to spec:
let i = 0;
for (const len = 3; i < len; i++) {
console.log(i);
}
// From https://kangax.github.io/compat-table/es6/#test-const
for (const baz = 0; false;) {}
// Yay, a const counter! ...uh
for (const counter = {i: 0}; counter.i < 3; counter.i++) {
console.log(counter.i);
}
It is probably not particularly useful though...
Why does it work?
The standard says:
5. For each element dn of boundNames do
a. If isConst is true, then
i. Perform loopEnv.CreateImmutableBinding(dn, true).
...where boundNames refers to the const binding. As you can see, the standard allows const 'loop counters', but doesn't say that you will be able to re-assign (increment) them later on (which, in fact, does not work).

Explanation of `let` and block scoping with for loops

I understand that let prevents duplicate declarations which is nice.
let x;
let x; // error!
Variables declared with let can also be used in closures which can be expected
let i = 100;
setTimeout(function () { console.log(i) }, i); // '100' after 100 ms
What I have a bit of difficulty grasping is how let applies to loops. This seems to be specific to for loops. Consider the classic problem:
// prints '10' 10 times
for (var i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
// prints '0' through '9'
for (let i = 0; i < 10; i++) { process.nextTick(_ => console.log(i)) }
Why does using let in this context work? In my imagination even though only one block is visible, for actually creates a separate block for each iteration and the let declaration is done inside of that block ... but there is only one let declaration to initialize the value. Is this just syntactic sugar for ES6? How is this working?
I understand the differences between var and let and have illustrated them above. I'm particularly interested in understanding why the different declarations result in different output using a for loop.
Is this just syntactic sugar for ES6?
No, it's more than syntactic sugar. The gory details are buried in §13.6.3.9
CreatePerIterationEnvironment.
How is this working?
If you use that let keyword in the for statement, it will check what names it does bind and then
create a new lexical environment with those names for a) the initialiser expression b) each iteration (previosly to evaluating the increment expression)
copy the values from all variables with those names from one to the next environment
Your loop statement for (var i = 0; i < 10; i++) process.nextTick(_ => console.log(i)); desugars to a simple
// omitting braces when they don't introduce a block
var i;
i = 0;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
i++;
…
while for (let i = 0; i < 10; i++) process.nextTick(_ => console.log(i)); does "desugar" to the much more complicated
// using braces to explicitly denote block scopes,
// using indentation for control flow
{ let i;
i = 0;
__status = {i};
}
{ let {i} = __status;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
if (i < 10)
process.nextTick(_ => console.log(i))
__status = {i};
} { let {i} = __status;
i++;
…
I found this explanation from Exploring ES6 book the best:
var-declaring a variable in the head of a for loop creates a single
binding (storage space) for that variable:
const arr = [];
for (var i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [3,3,3]
Every i in the bodies of the three arrow functions refers to the same
binding, which is why they all return the same value.
If you let-declare a variable, a new binding is created for each loop
iteration:
const arr = [];
for (let i=0; i < 3; i++) {
arr.push(() => i);
}
arr.map(x => x()); // [0,1,2]
This time, each i refers to the binding of one specific iteration and
preserves the value that was current at that time. Therefore, each
arrow function returns a different value.
let introduces block scoping and equivalent binding, much like functions create a scope with closure. I believe the relevant section of the spec is 13.2.1, where the note mentions that let declarations are part of a LexicalBinding and both live within a Lexical Environment. Section 13.2.2 states that var declarations are attached to a VariableEnvironment, rather than a LexicalBinding.
The MDN explanation supports this as well, stating that:
It works by binding zero or more variables in the lexical scope of a single block of code
suggesting that the variables are bound to the block, which varies each iteration requiring a new LexicalBinding (I believe, not 100% on that point), rather than the surrounding Lexical Environment or VariableEnvironment which would be constant for the duration of the call.
In short, when using let, the closure is at the loop body and the variable is different each time, so it must be captured again. When using var, the variable is at the surrounding function, so there is no requirement to reclose and the same reference is passed to each iteration.
Adapting your example to run in the browser:
// prints '10' 10 times
for (var i = 0; i < 10; i++) {
setTimeout(_ => console.log('var', i), 0);
}
// prints '0' through '9'
for (let i = 0; i < 10; i++) {
setTimeout(_ => console.log('let', i), 0);
}
certainly shows the latter printing each value. If you look at how Babel transpiles this, it produces:
for (var i = 0; i < 10; i++) {
setTimeout(function(_) {
return console.log(i);
}, 0);
}
var _loop = function(_i) {
setTimeout(function(_) {
return console.log(_i);
}, 0);
};
// prints '0' through '9'
for (var _i = 0; _i < 10; _i++) {
_loop(_i);
}
Assuming that Babel is fairly conformant, that matches up with my interpretation of the spec.
Recently I got confused about this problem too. According to the above answers, here is my understanding:
for (let i=0;i<n;i++)
{
//loop code
}
is equivalent to
// initial
{
let i=0
}
// loop
{
// Sugar: For-Let help you to redefine i for binding it into current block scope
let i=__i_value_from_last_loop__
if (i<=n){
//loop code
}
i++
}
Let us see “let” and “var” with the setTimeout majorly asked in the interview.
(function timer() {
for (var i=0; i<=2; i++)
{ setTimeout(function clog() {console.log(i)}, i*1000); }
})();
(function timer() {
for (let i=0; i<=2; i++)
{ setTimeout(function clog() {console.log(i)}, i*1000); }
})();
Let's see in detail how this code executes in the javascript compiler.
The answer for “var” is “222” due to functional scope and for “let” is “012” because it is block scope.
Now let us look at what it looks like in detail when it Compiles for "var". (It's a little hard to explain over the code than in audio or video but I am trying my best to give you.)
var i = 0;
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 1
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 2
if(i <=2){
setTimeout(() => console.log(i));
}
i++; // here the value of "i" will be 3
After the code is executed finally it will print all the console.log where the value of "i" is 6. So the final output is: 222
In "let i" will be declared in every scope.
The import point to be noted here is "i" will get the value from the previous scope and not from the declaration. (Below code is just an example of how it looks like in the compiler and trying it wont work)
{
//Scope 1
{
let i;
i= 0;
if(i<=2) {
setTimeout(function clog() {console.log(i)};);
}
i++; // Here "i" will be increated to 1
}
//Scope 2
// Second Interation run
{
let i;
i=0;
// Even “i” is declared here i= 0 but it will take the value from the previous scope
// Here "i" take the value from the previous scope as 1
if(i<=2) {
setTimeout(function clog() {console.log(i)}; );
}
i++; // Here “i” will be increased to 2
}
//Scope 3
// Second Interation run
{
let i;
i=0;
// Here "i" take the value from the previous scope as 2
if(i<=2) {
setTimeout(function clog() {console.log(i)}; );
}
i++; // Here "i" will be increated to 3
}
}
So, it will print "012" value as per the block scope.

Categories

Resources