Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

The let command

Basic usage

ES6 added the let command to declare variables. Its use is similar to var, but the declared variable is only valid within the code block ({}) in which the let command resides.

{
  let a = 10;
  var b = 1;
}
​
a // ReferenceError: a is not defined.
b // 1
Copy the code

The above code declares two variables, let and var, respectively, in the code block. These two variables are then called outside of the code block, with the let variable reporting an error and the var variable returning the correct value. This indicates that the variable declared by the LET is valid only in the code block in which it is located.

A counter for a loop is suitable for the let command.

for (let i = 0; i < 10; i++) {
  // ...
}
​
console.log(i);
// ReferenceError: i is not defined
Copy the code

In the code above, the counter I is only valid inside the for loop, and references outside the loop report an error.

The following code, if var is used, will output 10.

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

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. The value of variable I changes each time through the loop, and the console.log(I) inside the function assigned to array A refers to the global I. That is, all the I’s in array A refer to the same I, causing the runtime to print the value of the last round of I, which is 10.

If let is used, the declared variable is only valid in the block-level scope, and the final output is 6.

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

In the above code, the variable I is declared by let, and the current I is only valid for this loop, so each loop I is actually a new variable, so the final output is 6. You might ask, if the variable I for each cycle is redeclared, how does it know the value of the previous cycle and thus calculate the value of the current cycle? This is because the JavaScript engine internally remembers the value of the previous cycle, and when it initializes the variable I of this cycle, it evaluates on the basis of the previous cycle.

Another special feature of the for loop is that the part that sets the variables of the loop is a parent scope, while the inside of the loop body is a separate child scope. (Scope independent)

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

The above code works correctly, printing three ABCs. This indicates that the variable I inside the function and the loop variable I are not in the same scope and have separate scopes.

There is no variable promotion

The “variable promotion” phenomenon occurs when the var command is used, that is, the variable can be used before the declaration and its value is undefined. This is somewhat strange, as normal logic dictates that variables should be used only after the statement has been declared.

To correct this, the let command changes the syntactic behavior so that the variables it declares must be used after the declaration or an error will be reported.

// var case console.log(foo); Var foo = 2; // let's case console.log(bar); // ReferenceError let bar = 2;Copy the code

In the above code, the variable foo is declared with the var command, and the variable promotion occurs. That is, when the script starts running, the variable foo already exists, but has no value, so undefined is printed. The 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 an error will be thrown if it is used.

Temporary dead zone

As long as the let command exists in the block-level scope, the variables it declares are “binding” to the region, no longer subject to external influence.

var tmp = 123;
​
if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
Copy the code

In the above code, there is a global variable TMP, but in the block-level scope, let also declared a local variable TMP, causing the latter to bind the block-level scope. Therefore, before let declared the variable, TMP assignment will report an error.

ES6 explicitly states that if there are let and const commands in a block, the variables declared by the block to those commands form a closed scope from the start. Any time these variables are used before declaration, an error is reported.

In short, the variable is not available within the code block until it is declared using the let command. This is grammatically known as a “temporal dead zone” (TDZ).

If (true) {// TMP = 'ABC '; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ end console.log(TMP); // undefined tmp = 123; console.log(tmp); / / 123}Copy the code

In the above code, the TMP variable is in the “dead zone” of TMP until the let command declares it.

A “temporary dead zone” also means that Typeof is no longer a 100 percent secure operation.

typeof x; // ReferenceError
let x;
Copy the code

In the above code, variable X is declared using let command, so before declaration, it belongs to the “dead zone” of X, and an error will be reported whenever this variable is used. Therefore, the Typeof runtime throws a ReferenceError.

By comparison, typeof does not generate an error if a variable is not declared at all.

console.log(typeof a); // "undefined" var a = 10;Copy the code

In the code above, the typeof A result returns “undefined”. So, before let, typeof operators are 100 percent safe and never report errors. That’s no longer true. This is designed to encourage good programming practice. Variables must be used after declaration, otherwise an error will be reported.

Some “dead zones” are hidden and hard to spot.

Function bar(x = y, y = 2) {return [x, y]; } bar(); / / an errorCopy the code

In the above code, calling bar fails (and some implementations may not) because the default value of parameter x is equal to another parameter y, which has not yet been declared and is a “dead zone.” If y defaults to x, no error is reported because x is already declared.

function bar(x = 2, y = x) { return [x, y]; } bar(); / / (2, 2)Copy the code

In addition, the following code also reports an error, which behaves differently from var.

Var x = x; // let x = x; // ReferenceError: x is not definedCopy the code

The above code error is also due to temporary dead zone. When declaring a variable with let, an error is reported whenever the variable is used before the declaration is complete. The above line is an example of this situation, where the value of x is fetched before the declaration of variable x is completed, causing an error “x undefined”.

ES6 makes it clear that temporary dead zones and let and const statements do not promote variables. The main purpose of ES6 is to reduce runtime 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 provision, it’s easy to avoid them.

In short, the essence of a temporary dead zone is that the variable you want to use already exists as soon as you enter the current scope, but you can’t get it until the line of code that declared the variable appears.

Duplicate declarations are not allowed

Let does not allow the same variable to be declared twice in the same scope.

Function func() {let a = 10; var a = 1; Function func() {let a = 10; let a = 1; }Copy the code

Therefore, arguments cannot be redeclared inside functions.

function func(arg) { let arg; Function func(arg) {{let arg; }} func(Copy the code

Block-level scope

Why do you need block-level scopes?

ES5 has only global and functional scopes, not block-level scopes, which leads to a lot of irrational scenarios.

In the first scenario, the inner variables may override the outer variables.

var tmp = new Date();
​
function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
​
f(); // undefined
Copy the code

The if block uses the TMP variable in the outer layer and the TMP variable in the inner layer. The TMP variable in the inner layer overwrites the TMP variable in the outer layer.

In the second scenario, loop variables used to count are exposed as global variables.

var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); / / 5Copy the code

In the code above, the variable I is used only to control the loop, but when the loop ends, it does not disappear and leaks out as a global variable.

ES6 block-level scope

Let actually adds block-level scope to JavaScript.

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
Copy the code

The above function has two code blocks that declare the variable n and print 5. This means that the outer code block is not affected by the inner code block. If you use var to define n both times, the output is 10.

ES6 allows for arbitrary nesting of block-level scopes.

{{{{ {let insane = 'Hello World'} console.log(insane); / / error}}}};Copy the code

The code above uses a five level block-level scope, with each level being a separate scope. The fourth scope cannot read the internal variables of the fifth scope.

The inner scope can define variables of the same name for the outer scope.

{{{{
  let insane = 'Hello World';
  {let insane = 'Hello World'}
}}}};
Copy the code

The emergence of block-level scopes virtually eliminates the need for widely used anonymous instant-execution function expressions (anonymous IIFE).

// IIFE; (function () { var tmp = ... ; . } ()); IIFE benefits: 1. Avoid scope naming contamination 2. Improve performance (reduce scope lookups) 3. // block scope {let TMP =... ; . }Copy the code

Block-level scopes and function declarations

Can functions be declared in block-level scopes? This is a rather confusing question.

ES5 states that functions can only be declared in top-level scopes and function scopes, not block-level scopes.

/ / is the if (true) {function f () {}} / / case 2 try {function f () {}} the catch (e) {/ /... }Copy the code

Both of the above function declarations are illegal under ES5.

However, browsers do not comply with this rule and, for compatibility with older code, still support declaring functions in block-level scopes, so both cases actually work without error.

ES6 introduces block-level scopes, explicitly allowing functions to be declared in block-level scopes. ES6 states that a function declaration statement in a block-level scope behaves like a LET and cannot be referenced outside the block-level scope.

function f() { console.log('I am outside! '); } (function () {if (false) {function f() {console.log('I am inside! '); } } f(); } ());Copy the code

Running the above code on ES5, you get “I am inside!” Because f declared inside if is promoted to the function header, the actual code to run is as follows.

Function f() {console.log('I am outside! '); } (function () { function f() { console.log('I am inside! '); } if (false) { } f(); } ());Copy the code

The ES6 is completely different, theoretically getting “I am outside!” . Because functions declared inside the block-level scope are like lets, they have no effect outside the scope. However, if you actually run the above code in an ES6 browser, you will get an error. Why?

Function f() {console.log('I am outside! '); } (function () {if (false) {function f() {console.log('I am inside! '); } } f(); } ()); // Uncaught TypeError: f is not a functionCopy the code

The above code will always report an error in an ES6 browser.

It turns out that if you change the processing rules for functions declared in block-level scopes, you can obviously have a big impact on older code. To mitigate the resulting incompatibilities, ES6 provides in Appendix B that browser implementations can behave in their own way.

  • Allows functions to be declared in block-level scopes.
  • The function declaration looks something likevarIs promoted to the head of the global scope or function scope.
  • Function declarations are also promoted to the head of their block-level scope.

Note that the above three rules apply only to browser implementations of ES6. Implementations of other environments do not follow these rules, and block-scoped function declarations are treated as lets.

According to these three rules, block-level scoped functions in the browser’s ES6 environment behave like variables declared by var. The above example actually runs the following code.

Function f() {console.log('I am outside! '); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside! '); } } f(); } ()); // Uncaught TypeError: f is not a functionCopy the code

You should avoid declaring functions in block-level scopes because of the wide variation in behavior caused by the environment. If necessary, write it as a function expression rather than a function declaration statement.

{let a = 'secret'; function f() { return a; }} // Inside the block-level scope, the function expression {let a = 'secret'; let f = function () { return a; }; }Copy the code

There is another caveat. ES6 block-level scopes must have braces, and if there are no braces, the JavaScript engine assumes that there is no block-level scope.

If (true) let x = 1; If (true) {let x = 1; }Copy the code

In the above code, the first method has no braces, so there is no block-level scope, and the let can only appear at the top level of the current scope, so an error is reported. The second version has braces, so the block-level scope holds.

Const command

Basic usage

Const declares a read-only constant. Once declared, the value of a constant cannot be changed.

Const PI = 3.1415; PI = 3; // TypeError: Assignment to constant variable.Copy the code

The above code shows that changing the value of a constant causes an error.

A variable declared by const may not change its value, which means that a const, once declared, must be initialized immediately and cannot be left for later assignment.

const foo;
// SyntaxError: Missing initializer in const declaration
Copy the code

The code above shows that const is declared without assignment, and an error is reported.

Const has the same scope as the let command: it is only valid in the block-level scope in which the declaration is made.

if (true) {
  const MAX = 5;
}
​
MAX // Uncaught ReferenceError: MAX is not defined
Copy the code

Constants declared by the const command are also non-promoted and have temporary dead zones that can only be used after the declared position.

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}
Copy the code

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

A constant declared by const, like let, cannot be declared repeatedly.

var message = "Hello!" ; let age = 25; Const message = "Goodbye!" ; const age = 30;Copy the code

nature

What const actually guarantees is not that the value of the variable cannot be changed, but that the data stored at the memory address to which the variable points cannot be changed. For data of simple types (values, strings, booleans), the value is stored at the memory address to which the variable points, and is therefore equivalent to a constant. But for complex type data (mainly objects and arrays), variable pointing to the memory address, save only a pointer to the actual data, const can guarantee the pointer is fixed (i.e., always points to the other a fixed address), as far as it is pointing to the data structure of variable, is completely unable to control. Therefore, declaring an object as a constant must be done with great care.

const foo = {}; // Add an attribute to foo, which succeeds foo.prop = 123; Foo. Prop // 123 // Pointing foo to another object will result in an error foo = {}; // TypeError: "foo" is read-onlyCopy the code

In the code above, the constant foo stores an address that points to an object. Only the address is immutable, that is, you can’t point foo to another address, but the object itself is mutable, so you can still add new attributes to it.

Here’s another example.

const a = []; a.push('Hello'); // execute a.length = 0; A = ['Dave']; / / an errorCopy the code

In the above code, the constant A is an array. The array itself is writable, but an error is reported if another array is assigned to A.

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

const foo = Object.freeze({}); // In normal mode, the following line does not work; // In strict mode, this line will report an error foo.prop = 123;Copy the code

In the code above, the constant foo refers to a frozen object, so adding new attributes does not work and will cause an error in strict mode.

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

var constantize = function(obj){ Object.freeze(obj); //Object.values(); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); }}); };Copy the code

ES6 six ways to declare variables

ES5 has only two methods for declaring variables: the var command and the function command. In addition to the let and const commands, ES6 has two other ways to declare variables, the import command and the class command, as discussed in a later section. So there are six ways to declare variables in ES6.

Properties of the top-level object

The top-level object refers to the Window object in the browser environment and the Global object in Node. In ES5, attributes of top-level objects are equivalent to global variables.

window.a = 1;
a // 1
​
a = 2;
window.a // 2
Copy the code

In the code above, the assignment of attributes to top-level objects is the same thing as the assignment of global variables.

The attributes of top-level objects are linked to global variables, which is considered one of the biggest design failures of the JavaScript language. There are several major problems with this design. First, there is no way to report an undeclared variable error at compile time, which is only known at run time (because global variables can be created by attributes of top-level objects, which are created dynamically). Second, it is easy for programmers to unknowingly create global variables (such as typos). Finally, the properties of the top-level object are read and written everywhere, which makes modular programming very difficult. On the other hand, the window object has an entity meaning, which refers to the browser window object, and the top-level object is an entity meaning object, which is also inappropriate.

In order to change this, ON the one hand, ES6 stipulates that, in order to maintain compatibility, global variables declared by var and function commands are still attributes of top-level objects. On the other hand, global variables declared by let, const, and class are not attributes of the top-level object. That is, starting with ES6, global variables will gradually be decoupled from the properties of top-level objects.

var a = 1; A // 1 let b = 1; // This. A window. A // 1 let b = 1; window.b // undefinedCopy the code

In the code above, the global variable A is declared by the var command, so it is a property of the top-level object; The global variable B is declared by the let command, so it is not a property of the top-level object and returns undefined.

GlobalThis object

The JavaScript language has a top-level object that provides the global environment (that is, the global scope) in which all code runs. However, top-level objects are not uniform across implementations.

  • In the browser, the top-level object iswindow, but Node and Web workers do notwindow.
  • In browsers and Web workers,selfAlso points to top-level objects, but Node doesn’tself.
  • In Node, the top-level object isglobal, but no other environment supports it.

In order for the same code to be able to fetch the top-level object in any environment, it is now common to use this variable, but there are limitations.

  • In the global environment,thisReturns the top-level object. However, in the Node.js modulethisReturn the current module, ES6 modulethisReturns theundefined.
  • Inside the functionthisIf a function is not run as a method of an object, but simply as a function,thisIt points to the top-level object. But, in strict mode, thenthisReturns theundefined.
  • Whether it’s strict mode or normal mode,new Function('return this')()The global object is always returned. However, if the browser uses a Content Security Policy (CSP), theneval,new FunctionNone of these methods may work.

In summary, it’s hard to find a way to get to the top-level object in all cases.

ES2020 introduces globalThis as a top-level object at the level of language standards. That is, globalThis exists in any context and can be retrieved from it to point to the top-level object pointing to this in the global context.

//demo.js // browser environment: console.log(globalThis); //window // Node environment console.log(globalThis); //globalCopy the code