The emergence of block-level scopes

Variables declared by var have the property of variable promotion:

if (condition) {
    var value = 1;
}
console.log(value);
Copy the code

If condition is false, the result should be an error. However, because the variable is promoted, the code is equivalent to:

var value;
if (condition) {
    value = 1;
}
console.log(value);
Copy the code

If condition is false, the result will be undefined.

In addition, in the for loop:

for (var i = 0; i < 10; i++) {
    ...
}
console.log(i); / / 10
Copy the code

We can still access the value of I even after the loop has ended.

To increase control over the life cycle of variables, ECMAScript 6 introduces block-level scopes.

Block-level scopes exist in:

  • Function of the internal
  • Block (area between characters {and})

Let and const

Block-level declarations are used to declare variables that cannot be accessed outside the scope of the specified block.

Let and const are both block-level declarations.

Let’s review the characteristics of lets and const:

1. You won’t be promoted

if (false) {
    let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
Copy the code

2. Duplicate statements report errors

var value = 1;
let value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared
Copy the code

3. Do not bind global scopes

When a var declaration is used in a global scope, a new global variable is created as a property of the global object.

var value = 1;
console.log(window.value); / / 1
Copy the code

However, let and const do not:

let value = 1;
console.log(window.value); // undefined
Copy the code

Let vs. const

Const is used to declare constants. Once set, it cannot be changed or an error is reported.

It is worth noting that const declarations do not allow binding changes, but values to be changed. This means that when declaring an object with const:

const data = {
    value: 1
}

// No problem
data.value = 2;
data.num = 3;

/ / an error
data = {}; // Uncaught TypeError: Assignment to constant variable.
Copy the code

Temporary dead zone

Temporal Dead Zone, short for TDZ.

Variables declared by let and const are not promoted to the top of scope. If they are accessed before the declaration, an error will be reported:

console.log(typeof value); // Uncaught ReferenceError: value is not defined
let value = 1;
Copy the code

This is because when the JavaScript engine scans the code for variable declarations, it either promotes them to the top of the scope (encountering var declarations) or places the declarations in TDZ (encountering let and const declarations). Accessing variables in TDZ triggers a runtime error. Variables are removed from TDZ and can be accessed only after a variable declaration statement has been executed.

Seemingly understandable, there is no guarantee that you will not make mistakes:

var value = "global";

1 / / examples
(function() {
    console.log(value);

    let value = 'local'; } ());2 / / examples
{
    console.log(value);

    const value = 'local';
};
Copy the code

In both cases, the result does not print “global”, but Uncaught ReferenceError: value is not defined, because of TDZ.

Block-level scope in a loop

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0] ();/ / 3
Copy the code

Here’s the solution to a well-worn interview question:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(i){
        return function() {
            console.log(i);
        }
    }(i))
}
funcs[0] ();/ / 0
Copy the code

ES6’s LET provides a new solution to this problem:

var funcs = [];
for (let i = 0; i < 3; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0] ();/ / 0
Copy the code

Let is not promoted, cannot be declared repeatedly, cannot bind to global scope, etc., but why print I correctly here?

If it is not repeat statement, the second time in the cycle, is declared with the let again I, should be an error, even if for some reason, repeat the statement is not an error, again and again iteration, eventually I value should be 3 ah, others say set loop variables that part of the for loop is a single scope, such as:

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
Copy the code

This example is correct. What if we change let to var?

for (var i = 0; i < 3; i++) {
  var i = 'abc';
  console.log(i);
}
// abc
Copy the code

Why is the result different? If there is a separate scope, the result should be the same…

If you want to pursue this problem, you have to discard these features! This is because the behavior of let declarations within loops is specifically defined in the standard and is not necessarily related to the unpromoted nature of let, which was not included in earlier LET implementations.

We look at section 13.7.4.7 of the ECMAScript specification:

We’ll see that when we use let and var in the for loop, the underlying processing is different.

So what does the bottom line do when using let?

For (let I = 0; i < 3; I++) creates a hidden scope inside the parentheses, which explains why:

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
Copy the code

Each iteration loop then creates a new variable and initializes it with the value of the variable of the same name from the previous iteration. So for a piece of code like this

var funcs = [];
for (let i = 0; i < 3; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0] ();/ / 0
Copy the code

Equivalent to:

/ / pseudo code
(let i = 0) {
    funcs[0] = function() {
        console.log(i)
    };
}

(let i = 1) {
    funcs[1] = function() {
        console.log(i)
    };
}

(let i = 2) {
    funcs[2] = function() {
        console.log(i)
    };
};
Copy the code

When a function is executed, the correct value is found according to lexical scope. You can also think of the let declaration as mimicking closures to simplify the loop.

Let and const in the loop

But we’re not done here. What if we change let to const?

var funcs = [];
for (const i = 0; i < 10; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0] ();// Uncaught TypeError: Assignment to constant variable.
Copy the code

The result is an error, because although we create a new variable each time, we are trying to modify the value of const during the iteration, so we end up with an error.

We also have for in loops

So what’s the result here?

var funcs = [], object = {a: 1.b: 1.c: 1};
for (var key in object) {
    funcs.push(function(){
        console.log(key)
    });
}

funcs[0] ()Copy the code

The result is a ‘c’;

What if we change var to let or const?

Using let, the result would naturally be ‘a’, const? Error or ‘A ‘?

The result is the correct ‘a’ print, because in the for in loop, each iteration does not modify an existing binding, but creates a new binding.

Babel

How do you compile lets and const in Babel? Let’s look at the compiled code:

let value = 1;
Copy the code

Compile as follows:

var value = 1;
Copy the code

We can see that Babel compiles let directly to var. If so, let’s write an example:

if (false) {
    let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
Copy the code

If it were compiled directly to var, it would print undefined. However, Babel is clever enough to compile:

if (false) {
    var _value = 1;
}
console.log(value);
Copy the code

Let’s do another intuitive example:

let value = 1;
{
    let value = 2;
}
value = 3;
Copy the code
var value = 1;
{
    var _value = 2;
}
value = 3;
Copy the code

The essence is the same, is to change the quantity name, so that the inner and outer layer variable name is different.

What about things like const changes reporting errors and repeated declarations reporting errors?

In fact, it will give you an error at compile time…

What about the let declaration in the loop?

var funcs = [];
for (let i = 0; i < 10; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0] ();/ / 0
Copy the code

Babel’s clever translation reads:

var funcs = [];

var _loop = function _loop(i) {
    funcs[i] = function () {
        console.log(i);
    };
};

for (var i = 0; i < 10; i++) {
    _loop(i);
}
funcs[0] ();/ / 0
Copy the code

Best practices

At the time of development, we might have thought that we would default to let rather than var, in which case we would use const for variables that need write protection. However, another approach is becoming more common: use const by default and let only when you really need to change the value of a variable. This is because most variable values should not change after initialization, and unexpected variable changes are the source of many bugs.

ES6 series

ES6 directory address: github.com/mqyqingfeng…

ES6 series is expected to write about 20 chapters, aiming to deepen the understanding of ES6 knowledge points, focusing on the block-level scope, tag template, arrow function, Symbol, Set, Map and Promise simulation implementation, module loading scheme, asynchronous processing and other contents.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.