Explanation of `let` and block scoping with for loops - 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);
}

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.

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

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.

JS: Variable scopes and event-loop

I'd like to understand the following code:
for (var i=0; i<3; i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
console.log('After the Loop');
Prints After the loop,3,3,3
for (let i=0; i<3; i++){
setTimeout(() => {
console.log(i);
}, 1000);
}
console.log('After the Loop');
Prints After the loop,0,1,2
I know that the events are executed after the main, so the numbers of i appear after After the loop. And letmeans block scope (so iis visible in for-loop only) and varfunction scope (so it should be visible globally here), but I still don't see why the first prints 3,3,3.
Can anyone please provide a resonable explaination?
Thanks!
When you use var, the same i is shared by all the anonymous functions passed to setTimeout(). The timeouts fire a relatively long time after the loop finishes, so by that time the value of the (shared) i is 3.
Consider the following examples which show i defined using var and let and how scope is determined at the function and block level.
// "i" defined with "var" within the function scope
function foo() {
var i;
for (i = 0; i < 3; i++) {
setTimeout(() => console.log('foo', i), 250);
}
// output: 3 3 3
}
// "i" defined with "let" within the function scope
function bar() {
let i;
for (i = 0; i < 3; i++) {
setTimeout(() => console.log('bar', i), 250);
}
// output: 3 3 3
}
// "i" is defined within the block however it gets hoisted up
// to the function scope as this is the behavior of "var"
function baz() {
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('baz', i), 250);
}
// output: 3 3 3
}
// "i" is defined within the block and "let" allows "i" to maintain the block scope
function boo() {
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log('boo', i), 250);
}
// output: 0 1 2
}

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).

Categories

Resources