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).
Related
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.
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.
Wondering if there is by any chance to programmatically setting third statement of forloop
var conditionProgrammatically = 'i++';//or 'x--'
for (var i = 0; i < 10; conditionProgrammatically) {
console.log(i)
}
You can use any expression you want there including calling a function. You just need to be careful of scope. So, for example, this works:
var conditionProgramatically = () => i++ ;
for (var i = 0; i < 10; conditionProgramatically()) {
console.log(i)
}
But it depends on the fact that var i is in a scope shared by the function. This, however, doesn't work:
var conditionProgramatically = () => i++ ;
for (let i = 0; i < 10; conditionProgramatically()) {
console.log(i)
}
Because let is scoped to the block and not available.
Of course you can share an object which is mutable by passing it as an argument like:
fn = (o) => o.i += 1
for (let o = {i:0}; o.i < 10; fn(o)) {
console.log(o.i)
}
This allows you to use let, but is a little hard on the eyes.
All said, it's probably going to be easier to make your logic fit in a simple expression rather than calling a function. You can still perform some logic, though:
for (let i = 0; Math.abs(i) < 10; i = Math.random() > .65 ? i -1: i + 1) {
console.log(i)
}
You can set a variable and then operate with this variable according to your needs.
(remember that i-- is equivalent to i -= 1).
BTW, be careful because you would also have to change the condition, if not you will end up in an infinite loop. In your case, I would use abs()
var step = 1; // or var step = -1;
for (var i = 0; abs(i) < 10; i += step) {
console.log(i)
}
Usually, in functional programmings (like python and javascript), we can use dictionary (or objects) to store functions.
var myFunctions = {
"a": function (i) { return i + 1 },
"b": function (i) { return i - 3 }
};
Then, we can set the condition as the key to the dictionary:
myCondition = "a"; // this will set condition to increment by 1
Here is your for loop:
for (i = 0; i < n; i = myFunctions[myCondition](i)) {
// whatever
}
I am really confused about this code
var box = document.getElementsByClassName('box-value');
for(let i = 0; i < box.length; i++){
box[i].onclick = function(){
console.log(i);
}
console.log("End loop. i:" + i);
}
let i = 0;
box[i].onclick = function(){
console.log(i);
}
i = 9;
box[0].onclick();
In the first block, i is 0
But in the second block, i is 9.
I really don't understand why?
Because your first i is in a block and doesn't get changed afterwards, while your second i (is not in a block) and does get set to 9 before the click handler is run. You can emulate the behaviour from the loop by doing
{
let i = 0; // one variable that stays constant
box[i].onclick = function(){
console.log(i);
};
}
let i = 9; // a different variable
and you can also emulate the altering behaviour of the assignment by putting the scope around the loop:
let i = 0;
for(; i < box.length; i++) {
box[i].onclick = function() {
console.log(i);
};
console.log("End loop. i:" + i);
}
The i declared with let in the for loop won't exist after the loop ends. The second i is separate and you setting that to 9, that's why the value of the second i is 9.
let statement documentation
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.