Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

ES6 let with const


May 08, 2021 ES6


Table of contents


1. let command

Basic usage

ES6 let command to declare variables. It is similar to var in use, but the declared variable is valid only within the block of code where the let command is located.

  1. {
  2. let a = 10;
  3. var b = 1;
  4. }
  5. a // ReferenceError: a is not defined.
  6. b // 1

The above code is in the block of code and declares two variables with let and var, respectively. T he two variables are then called outside the block of code, resulting in an error in the variable declared by the let and the correct value returned by the variable declared by var. This indicates let declared by let is valid only in the block of code in which 代码块 is located.

For loop counters, it's a good fit to use the let command.

  1. for (let i = 0; i < 10; i++) {
  2. // ...
  3. }
  4. console.log(i);
  5. // ReferenceError: i is not defined

In the above code, counter i is only valid in the for loop body, and the in vitro reference of the loop will report an error.

If the following code uses var, the last output is 10.

  1. var a = [];
  2. for (var i = 0; i < 10; i++) {
  3. a[i] = function () {
  4. console.log(i);
  5. };
  6. }
  7. a[6](); // 10

In the above code, the variable i is declared by the var command and is valid globally, so there is only one variable i globally. E ach loop, the value of the variable i changes, and the console.log.log(i) inside the function assigned to array a within the loop points to the global i. That is, the i inside all the members of array a points to the same i, causing the runtime to output the value of the last round of i, which is 10.

If you use alet, the declared variable is valid only within the block-level scope, and the last output is 6.

  1. var a = [];
  2. for (let i = 0; i < 10; i++) {
  3. a[i] = function () {
  4. console.log(i);
  5. };
  6. }
  7. a[6](); // 6

In the above code, the variable i is declared by thelet, the current i is only valid in this cycle, so each loop i is actually a new variable, so the final output is 6. Y ou might ask, if the variable i of each cycle is redested, how does it know the value of the last cycle and calculate the value of the current cycle? This is because the value of the previous cycle is remembered inside the JavaScript engine, and when the variable i of the current round is initialized, it is calculated on the basis of the previous loop.

In addition, the for loop has a special feature, that the part that sets the loop variable is a parent scope, and the inside of the loop body is a separate child scope.

  1. for (let i = 0; i < 3; i++) {
  2. let i = 'abc';
  3. console.log(i);
  4. }
  5. // abc
  6. // abc
  7. // abc

The above code runs correctly and outputs abc 3 times. This indicates that the variable i inside the function is not in the same scope as the loop variable i, and has its own separate scope.

There is no variable promotion

var command has “变量提升” phenomenon, where the variable can be used before it is declared, with a value of undefined. It is somewhat strange that this behavior is more or less, and according to general logic, variables should not be available until after the statement has been declared.

To correct this behavior, the let command changes the grammatical behavior, and the variables it declares must 声明后 declaration, or an error will be reported.

  1. // var 的情况
  2. console.log(foo); // 输出undefined
  3. var foo = 2;
  4. // let 的情况
  5. console.log(bar); // 报错ReferenceError
  6. let bar = 2;

In the above code, the variable foo is declared with a var command, and a variable promotion occurs, i.e. when the script starts running, the variable foo already exists, but has no value, so it outputs underfined. T he variable bar is declared with the let command and no variable promotion occurs. This means that the variable bar does not exist until it is declared, and if it is used, an error is thrown.

Temporary dead zone

As long as the block-level scope let command, the variable it (binding) region and is no longer affected externally.

  1. var tmp = 123;
  2. if (true) {
  3. tmp = 'abc'; // ReferenceError
  4. let tmp;
  5. }

In the above code, there is a global variable tmp, but the block-level scope insidelet declares a local variable tmp, causing the latter to bind the block-level scope, so the tmp assignment is misalised before the let declares the variable.

ES6 makes it clear that if there are let and const commands in a block, the blocks declare variables for those commands, forming a closed scope from the start. Anyone who uses these variables before declaring them will report an error.

In summary, within a block of code, a variable is not available until it is declared using the let command. This is syntaxally called a "temporary dead zone" (temporal dead zone, or TDZ).

  1. if (true) {
  2. // TDZ开始
  3. tmp = 'abc'; // ReferenceError
  4. console.log(tmp); // ReferenceError
  5. let tmp; // TDZ结束
  6. console.log(tmp); // undefined
  7. tmp = 123;
  8. console.log(tmp); // 123
  9. }

In the above code, the let command declares the variable tmp until it belongs to the "dead zone" of the variable tmp.

A "temporary dead zone" also means that typeof is no longer a 100 percent safe operation.

  1. typeof x; // ReferenceError
  2. let x;

In the code above, variable x is declared using the let command, so it belongs to the "dead zone" of x until it is declared, and errors are reported whenever the variable is used. Therefore, the typeof runtime throws a ReferenceError.

As a comparison, if a variable is not declared at all, the use of typeof does not report an error.

  1. typeof undeclared_variable // "undefined"

In the above code, undeclared_variable is a variable name that does not exist, and the result returns "undefined". T herefore, the typeof operator is 100% secure and never reports errors until there is no let. N ow that's not the point. This design is designed to allow everyone to develop good programming habits, variables must be used after the declaration, otherwise it will be reported wrong.

Some "dead zones" are more hidden and less easy to find.

  1. function bar(x = y, y = 2) {
  2. return [x, y];
  3. }
  4. bar(); // 报错

In the above code, the call bar function is called wrong (some implementations may not be wrong) because the parameter x default value is equal to another argument y, at this point y has not been declared, belongs to the "dead zone". If the default value of y is x, no errors will be reported because x is already declared at this point.

  1. function bar(x = 2, y = x) {
  2. return [x, y];
  3. }
  4. bar(); // [2, 2]

In addition, the following code will report errors, unlike var's behavior.

  1. // 不报错
  2. var x = x;
  3. // 报错
  4. let x = x;
  5. // ReferenceError: x is not defined

The above code is wrong, but also because of the temporary dead zone. W hen you use a let to declare a variable, you report an error as long as the variable is used before the declaration is complete. This is the case in the above line, where the value of x is taken before the declaration statement of variable x is executed, resulting in an error of "x undefined".

ES6 specifies that temporary dead zones and let and const statements do not have variable elevations, primarily to reduce run-time errors and prevent the variable from being used before it is declared, resulting in unexpected behavior. Such errors are common in ES5, and with this rule, it is easy to avoid them.

In short, the essence of a transverse dead zone is that as soon as you enter the current scope, the variable you want to use already exists, but it is not available, and you can only get and use the variable until the line of code that declares it appears.

Duplicate declarations are not allowed

let does not allow the same variable to be declared repeatedly within the same scope.

  1. // 报错
  2. function func() {
  3. let a = 10;
  4. var a = 1;
  5. }
  6. // 报错
  7. function func() {
  8. let a = 10;
  9. let a = 1;
  10. }

Therefore, arguments cannot be re-declared inside the function.

  1. function func(arg) {
  2. let arg;
  3. }
  4. func() // 报错
  5. function func(arg) {
  6. {
  7. let arg;
  8. }
  9. }
  10. func() // 不报错

2. Block-level scope

ES5 has 全局作用域 函数作用域 scope, which brings a lot of unreasonable scenarios.

In the first scenario, the inner variable may overwrite the outer variable.

  1. var tmp = new Date();
  2. function f() {
  3. console.log(tmp);
  4. if (false) {
  5. var tmp = 'hello world';
  6. }
  7. }
  8. f(); // undefined

The original idea of the above code is that the external use of the outer tmp variable, the internal use of the inner tmp variable. However, after the function f is executed, the output is underfined because the variable is raised, causing the inner tmp variable to overwrite the outer tmp variable.

In the second scenario, the loop variable used to count is leaked as a global variable.

  1. var s = 'hello';
  2. for (var i = 0; i < s.length; i++) {
  3. console.log(s[i]);
  4. }
  5. console.log(i); // 5

In the above code, the variable i is used only to control the loop, but after the loop ends, it does not disappear and is leaked as a global variable.

Block-level scope of ES6

let actually adds a block-level 块级作用域

  1. function f1() {
  2. let n = 5;
  3. if (true) {
  4. let n = 10;
  5. }
  6. console.log(n); // 5
  7. }

The above function has two blocks of code, both declaring variable n, and output 5 after running. T his means that the outer block of code is not affected by the inner block of code. If the variable n is defined with var on both occasions, the last output value is 10.

ES6 allows arbitrary nesting of block-level scopes.

  1. {{{{
  2. {let insane = 'Hello World'}
  3. console.log(insane); // 报错
  4. }}}};

The above code uses a five-layer block-level scope, each of which is a separate scope. The fourth-tier scope cannot read the internal variables of the fifth-tier scope.

An inner scope can define a variable of the same name for an outer scope.

  1. {{{{
  2. let insane = 'Hello World';
  3. {let insane = 'Hello World'}
  4. }}}};

The emergence of block-level scopes actually makes the widely used anonymous Immediate Execution Function Expression (Anonymous IIFE) unnecessary.

  1. // IIFE 写法
  2. (function () {
  3. var tmp = ...;
  4. ...
  5. }());
  6. // 块级作用域写法
  7. {
  8. let tmp = ...;
  9. ...
  10. }

Block-level scopes and function declarations

Can a function be declared in a block-level scope? This is a rather confusing question.

ES5 states that functions can 顶层作用域 函数作用域 scope and the function scope, not at the block-level scope.

  1. // 情况一
  2. if (true) {
  3. function f() {}
  4. }
  5. // 情况二
  6. try {
  7. function f() {}
  8. } catch(e) {
  9. // ...
  10. }

Both functions declare that both are illegal under ES5.

However, the browser does not comply with this rule, in order to be compatible with the old code before, or to support the declaration of functions in a block-level scope, so both of these cases can actually run without error reporting.

ES6 块级作用域 allowing functions to be declared in block-level scopes. ES6 states that in block-level scopes, function declaration statements behave like lets and cannot be referenced outside block-level scopes.

  1. function f() { console.log('I am outside!'); }
  2. (function () {
  3. if (false) {
  4. // 重复声明一次函数f
  5. function f() { console.log('I am inside!'); }
  6. }
  7. f();
  8. }());

The above code runs in ES5 and you get "I am inside!" because the function f declared in the if is promoted to the head of the function, and the actual code is run as follows.

  1. // ES5 环境
  2. function f() { console.log('I am outside!'); }
  3. (function () {
  4. function f() { console.log('I am inside!'); }
  5. if (false) {
  6. }
  7. f();
  8. }());

ES6 is completely different, theoretically getting "I am outside!". B ecause functions declared within a block-level scope are similar tolets, they have no effect outside the scope. But if you do run the code above in the ES6 browser, you're going to report an error, so why?

  1. // 浏览器的 ES6 环境
  2. function f() { console.log('I am outside!'); }
  3. (function () {
  4. if (false) {
  5. // 重复声明一次函数f
  6. function f() { console.log('I am inside!'); }
  7. }
  8. f();
  9. }());
  10. // Uncaught TypeError: f is not a function

The above code is reported as wrong in the ES6 browser.

It turns out that if you change the processing rules for functions declared within a block-level scope, it obviously has a big impact on the old code. In order to alleviate the resulting incompatibilities, ES6 states in Appendix B that browser implementations may not comply with the above regulations and have their own behavior.

  • Allows functions to be declared within a block-level scope.
  • Function declarations are similar to var, which is promoted to the head of a global scope or function scope.
  • At the same time, the function declaration is promoted to the head of the block-level scope in which it is located.

Note that the above three rules are only valid for browser implementations of ES6, and implementations in other environments do not have to be followed, or function declarations for block-level scopes are treated as lets.

According to these three rules, in the browser's ES6 environment, functions declared within a block-level scope behave like variables declared by var. The above example actually runs the code as follows.

  1. // 浏览器的 ES6 环境
  2. function f() { console.log('I am outside!'); }
  3. (function () {
  4. var f = undefined;
  5. if (false) {
  6. function f() { console.log('I am inside!'); }
  7. }
  8. f();
  9. }());
  10. // Uncaught TypeError: f is not a function

You should avoid declaring functions within block-level scopes, given that the behavior caused by the environment is too different. If you do, you should also write a function expression instead of a function declaration statement.

  1. // 块级作用域内部的函数声明语句,建议不要使用
  2. {
  3. let a = 'secret';
  4. function f() {
  5. return a;
  6. }
  7. }
  8. // 块级作用域内部,优先使用函数表达式
  9. {
  10. let a = 'secret';
  11. let f = function () {
  12. return a;
  13. };
  14. }

In addition, there is one area to note. The block-level scope of ES6 must have braces, and if there are no braces, the JavaScript engine considers that there is no block-level scope.

  1. // 第一种写法,报错
  2. if (true) let x = 1;
  3. // 第二种写法,不报错
  4. if (true) {
  5. let x = 1;
  6. }

In the above code, the first method does not have braces, so there is no block-level scope, and thelet can only appear at the top of the current scope, so errors are reported. The second type of writing has braces, so the block-level scope is established.

The same is true for function declarations, where, in strict mode, functions can only declare the top level of the current scope.

  1. // 不报错
  2. 'use strict';
  3. if (true) {
  4. function f() {}
  5. }
  6. // 报错
  7. 'use strict';
  8. if (true)
  9. function f() {}

3. Const command

Basic usage

const declares a 常量 Once declared, the value of the constant cannot be changed.

  1. const PI = 3.1415;
  2. PI // 3.1415
  3. PI = 3;
  4. // TypeError: Assignment to constant variable.

The above code indicates that changing the value of the constant will report an error.

The variable declared by the const must not change 初始化 the value, which means that once the variable is declared, the variable must be initialized immediately and cannot be left to be assigned later.

  1. const foo;
  2. // SyntaxError: Missing initializer in const declaration

The above code indicates that for const, simply declaring no assignment will result in an error.

The scope of the const is the same as the let command: valid only within the block-level scope 作用域 is located.

  1. if (true) {
  2. const MAX = 5;
  3. }
  4. MAX // Uncaught ReferenceError: MAX is not defined

The constant declared by the const command is also not elevated, and there is also a transative dead zone that can only be used after 位置后面 position.

  1. if (true) {
  2. console.log(MAX); // ReferenceError
  3. const MAX = 5;
  4. }

The above code is called before the constant MAX declaration, and the result is an error.

The constant declared by const is as non-repeatable as thelet.

  1. var message = "Hello!";
  2. let age = 25;
  3. // 以下两行都会报错
  4. const message = "Goodbye!";
  5. const age = 30;

The essence of const actually guarantees that the value of the variable cannot be changed, but that the data saved by the memory address to which the variable points must not be altered. F or simple types of data (values, strings, Boolean values), the value is saved at the memory address to which the variable points and is therefore equivalent to a constant. B ut for composite type of data (primarily objects and arrays), variables point to memory addresses, save only a pointer to the actual data, const can only guarantee that the pointer is fixed (that is, always point to another fixed address), as to whether the data structure it points to is variable, it is completely out of control. Therefore, you must be very careful about declaring an object as a constant.

  1. const foo = {};
  2. // 为 foo 添加一个属性,可以成功
  3. foo.prop = 123;
  4. foo.prop // 123
  5. // 将 foo 指向另一个对象,就会报错
  6. foo = {}; // TypeError: "foo" is read-only

In the code above, foo stores 地址 that points to an object. Immediosible is just this address, i.e. you can't point foo to another address, but the object itself is variable, so you can still add new properties to it.

Here's another example.

  1. const a = [];
  2. a.push('Hello'); // 可执行
  3. a.length = 0; // 可执行
  4. a = ['Dave']; // 报错

In the code above, constant a is an array that can be written by itself, but if you assign another array to a, you will report an error.

If you really want to freeze an object, you should use the Object.freeze method.

  1. const foo = Object.freeze({});
  2. // 常规模式时,下面一行不起作用;
  3. // 严格模式时,该行会报错
  4. foo.prop = 123;

In the code above, the constant foo points to a frozen object, so adding new properties doesn't work, and errors are reported in strict mode.

In addition to freezing the object itself, the properties of the object should also be frozen. Here's a function that freezes the object completely.

  1. var constantize = (obj) => {
  2. Object.freeze(obj);
  3. Object.keys(obj).forEach( (key, i) => {
  4. if ( typeof obj[key] === 'object' ) {
  5. constantize( obj[key] );
  6. }
  7. });
  8. };

ES6 declares six methods of variables

ES5 only two ways to declare variables: var command and function command. I n addition to adding let and let const ES6 mentions in the following sections that there are two other ways to declare variables: the import command and the class command. Therefore, ES6 has a total of 6 methods for declaring variables.

4. The properties of the top-level object

顶层对象 in 浏览器 window对象 and in Node to global对象 ES5 the properties of the top-level object are equivalent 等价 variables.

  1. window.a = 1;
  2. a // 1
  3. a = 2;
  4. window.a // 2

In the code above, the property assignment of the top-level object is the same as the assignment of the global variable.

The properties of the top-level object are linked to global variables and are considered one of the biggest design failures in the JavaScript language. S uch a design brings with it several big problems, first of all, it is not possible to report undered errors of variables at compile time, only at runtime to know (because global variables may be created by the properties of the top-level object, and the creation of properties is dynamic); On the other hand, window object has entity meaning, refers to the browser window object, the top-level object is an object with entity meaning, is also inappropriate.

ES6 To change this, ES6 states, on the one hand, that the global variables declared by the var and fusion commands are still properties of the top-level object, and on the other hand, that the global variables declared by the let command, the const command, and the class command are not properties of the top-level object. That is, starting with ES6, global variables are gradually decoupled from the properties of the top-level object.

  1. var a = 1;
  2. // 如果在 Node 的 REPL 环境,可以写成 global.a
  3. // 或者采用通用方法,写成 this.a
  4. window.a // 1
  5. let b = 1;
  6. window.b // undefined

In the above code, the global variable a is declared by the var command, so it is a property of the top-level object;

5. GlobalThis object

The JavaScript language has a top-level object that 全局环境 a global environment (that is, a global scope) in which all code runs. However, the top-level objects are not uniform within various implementations.

  • Inside the browser, the top-level object is window, but Node and Web Worker don't have window.
  • Inside browsers and web workers, self also points to top-level objects, but Node does not have self.
  • Inside Node, the top-level object is global, but other environments do not support it.

In order to be able to get top-level objects in a variety of environments, the same piece of code is now generally used with this variable, but with limitations.

  • In the global environment, this returns the top-level object. However, in the Node module and the ES6 module, this returns the current module.
  • Theis inside the function, if the function does not run as a method of the object, but simply as a function, this points to the top-level object. However, in strict mode, the this will return to theundefined.
  • Whether it's strict mode or normal mode, new Function ('return this') always returns a global object. However, if the browser uses CSP (Content Security Policy, Content Security Policy), the methods of eval and new Function may not be available.

In summary, it is difficult to find a way to get top-level objects in all cases. Here are two methods that you can barely use.

  1. // 方法一
  2. (typeof window !== 'undefined'
  3. ? window
  4. : (typeof process === 'object' &&
  5. typeof require === 'function' &&
  6. typeof global === 'object')
  7. ? global
  8. : this);
  9. // 方法二
  10. var getGlobal = function () {
  11. if (typeof self !== 'undefined') { return self; }
  12. if (typeof window !== 'undefined') { return window; }
  13. if (typeof global !== 'undefined') { return global; }
  14. throw new Error('unable to locate global object');
  15. };

ES2020 Introduces globalThis as a top-level object at the level of language standards. That is, in any environment, globalThis 存在 from which you can get the top-level object and point to the this in the global environment.

The gasket library global-this simulates this proposal and can be accessed in all environments.