Let in Javascript. Confusion - javascript

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

Related

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.

Loop - How to add onto a variable each loop

I am trying to add onto a variable each time a loop is ran. In this loop I want each loop to add hey to hey. So that hey is added 13 times. My loops is only adding it once which is confusing me. I am only trying to get this to show up in the console at the moment. Thank you!
const repeatString = function() {
let test = 'hey';
let add = 'hey';
for (let i = 0; i < 13; i++) {
return test += add;
}
}
console.log(repeatString());
You are returning from the function in the first iteration. You should return from outside of the loop (after the completion of the loop):
const repeatString = function() {
let test = 'hey';
let add = 'hey';
for (let i = 0; i < 13; i++) {
test += add;
}
return test;
}
console.log(repeatString());

Programmatically setting third statement of for loop

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
}

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