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.
Related
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.
I cannot understand why this code is behaving as it is:
for (var i = 1; i < 3; i++) {
var j;
if (!j) {
j = 1;
} else {
alert("why are we here? j shouldn't be defined but it's " + j);
}
}
(jsFiddle)
If I set j to null and check for null, it works the way I think it should.
This isn't how Java, C#, C++, etc. work, hence, the confusion.
This is because variables in JavaScript are scoped to functions (or the global scope if not within a function). A var is not scoped inside of a loop, and curly braces do not defined a new closure. Basically, the value of j persists after the loop body, and the var does not re-define j as undefined. This is why explicitly setting var j = null; has the expected effect.
Example 1:
A good way to think about this is, any time you declare a variable with var, like this.
function someFunc() {
for(var i = 0; i < 3; i++){
var j;
}
}
The interpreter hoist the variable declarations like so.
function someFunc() {
var i;
var j;
for(i = 0; i < 3; i++){
}
}
Notice that since the var j declaration is hoisted to the top of the function, the declaration actually does nothing within the loop.
Example 2:
However, if you were to initialize the variable with null like this.
function someFunc() {
for(var i = 0; i < 3; i++){
var j = null;
}
}
It would be interpreted like this.
function someFunc() {
var i;
var j;
for(i = 0; i < 3; i++){
j = null;
}
}
Notice how for every loop, j is set to null.
ES6 let keyword:
There is a keyword in ES6 which will create a scope in a loop like this, it is the let keyword. Keep in mind that browser support for the let keyword is poor at this point.
for (var i = 1; i < 3; i++) {
let j;
if (!j) {
j = 1;
} else {
alert("why are we here? j shouldn't be defined but it's "+ j);
}
}
The first time your for loop executes the body of the loop, j is undefined and thus your code sets j=1. On the subsequent iterations of the loop, j is already defined and set to 1 so it goes into your else clause as would be expected.
This occurs because variables defined with var in Javascript are function scoped, not block scoped and if not inside a function, then they are global. So, there is only one variable j in your jsFiddle and each iteration of the for loop uses the same variable (thus inheriting the value from the previous iteration).
It will work if you initialize j = null; inside the body of the for loop because then you're reinitializing it for each iteration rather than using the value from the previous iteration.
ES6 proposes to add the let declaration which would scope to the nearest block. See What's the difference between using "let" and "var" to declare a variable? for more info on let.
There isn't a block scope in JavaScript, only global and function scopes. Although, JavaScript variable statements are so flexible that they will give the programmer the illusion that a block scope could exist, but the truth is that variables declared in JavaScript functions are later hoisted by the interpreter. This means that their declarations are moved to the top of the most-recently declared function. Take this for example:
function test() {
console.log('a test');
for (var i = 0; i < 100; i++) {
var k = i + Math.random();
console.log(k)
}
}
The JavaScript interpreter, internally, will "transform" the code to the following:
function test() {
var i, k; // var declarations are now here!
console.log('a test');
for (i = 0; i < 100; i++) {
k = i + Math.random();
console.log(k)
}
}
It will move all the var declarations to the begining of the most recent function declaration. In your code, the hoisting will create the following code:
// A anonymous function created by jsFiddle to run your script
function () {
var i, j;
for (i = 1; i < 3; i++) {
if (!j) {
j = 1;
} else {
alert("Why are we here? j shouldn't be defined, but it's " + j);
}
}
}
The variable is undefined in the first time, then it gets assigned 1 and then your message is printed.