Explain the usage and difference of VaR, let and const in wechat applet

Time:2020-8-12

Wechat applets can be developed using the latest JavaScript ES6 standard, so the usage and difference of VaR, let and const in wechat applet can be regarded as the usage and difference of VaR, let and const in JavaScript ES6 standard

Let command

Basic Usage

ES6 adds the let command to declare variables. Its usage is similar to VaR, but the declared variable is only valid within the code block where the let command is located.


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

In the code block above, two variables are declared with let and VaR respectively. Then the two variables are called outside the code block. As a result, the variables declared by let report errors, and the variables declared by VAR return the correct values. This means that the variable declared by the let is only valid in the code block in which it is located.

For loop counter, it is suitable to use the let command.


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

In the above code, counter I is only valid in the for loop, and an error will be reported if it is referenced outside the circulation.

If VaR is used in the following code, the final output is 10.


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

In the above code, the variable I is declared by the VaR command and is valid in the global scope, so there is only one variable I in the global. In each loop, the value of the variable I changes, and the console.log (i) I in it points to the global I. In other words, I in all the members of array a point to the same I, which causes the runtime to output the value of I in the last round, that is, 10.

If you use let, the declared variable is only valid at 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](); // 6

In the above code, the variable I is declared by let. The current I is only valid in the current loop. Therefore, the I of each loop is actually a new variable, so the final output is 6. You may ask, if the variable I of each cycle is redeclared, how does it know the value of the previous cycle and calculate the value of the current cycle? This is because the JavaScript engine will remember the value of the previous cycle. When initializing the variable I of this round, it will be calculated on the basis of the previous cycle.

In addition, the for loop has a special feature, that is, the part where the loop variable is set is a parent scope, while the loop body is a separate child scope.


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

The above code runs correctly and outputs ABC three times. This shows that the variable I in the function is not in the same scope as the loop variable I, but has its own scope.

There is no variable promotion

The “variable promotion” phenomenon occurs in the VaR command, that is, the variable can be used before the declaration, and the value is undefined. This phenomenon is somewhat strange, according to the general logic, variables should be used after the declaration statement.

In order to correct this phenomenon, the let command changes the syntax behavior. The variables it declares must be used after the declaration, otherwise an error is reported.

//The case of VaR
console.log (foo); // output undefined
var foo = 2;
//Let
console.log (bar); // error referenceerror
let bar = 2;

In the above code, the variable foo is declared with the VaR command, and variable promotion will occur. That is, when the script starts to run, the variable foo already exists, but has no value, so it will output undefined. The variable bar is declared with the let command, and variable promotion does not occur. This means that the variable bar does not exist before it is declared, and an error will be thrown if it is used.

Temporary dead zone

As long as there is a let command in the block level scope, the variables it declares are bound to this area and are no longer affected by external factors.


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

In the above code, there is a global variable TMP, but let in the block level scope declares a local variable TMP, which results in the latter binding to the block level scope. Therefore, an error will be reported in the assignment of TMP before the let declares the variable.

ES6 clearly states that if there are let and const commands in a block, the variables declared by the block for these commands form a closed scope from the beginning. If you use these variables before you declare them, you will get an error.

In summary, variables are not available within the code block until they are declared using the let command. Grammatically, this is called “temporary dead zone” (TDZ).

if (true) {
 //TDZ start
 tmp = 'abc'; // ReferenceError
 console.log(tmp); // ReferenceError
 Let TMP; // end of TDZ
 console.log(tmp); // undefined
 tmp = 123;
 console.log(tmp); // 123
}

In the above code, before the let command declares the variable TMP, it belongs to the “dead zone” of the variable TMP.

“Temporary deadband” also means that typeof is no longer a 100% safe operation.


typeof x; // ReferenceError
let x;

In the above code, the variable x is declared with the let command, so before the declaration, it belongs to the “dead zone” of X. as long as the variable is used, an error will be reported. Therefore, the typeof runtime throws a referenceerror.

For comparison, if a variable is not declared at all, using typeof will not result in an error.


typeof undeclared_variable // "undefined"

In the above code, undeclared_ Variable is a nonexistent variable name. The result returns “undefined”. So, before let, the typeof operator is 100% safe and will never report an error. Now that’s not true. This design is to let everyone develop good programming habits, variables must be used after the declaration, otherwise an error will be reported.

Some “dead zones” are relatively hidden and not easy to find.

function bar(x = y, y = 2) {
 return [x, y];
}
Bar(); // error reported

In the above code, an error is reported when calling the bar function (some implementations may not report an error) because the default value of parameter x is equal to another parameter y. at this time, y has not been declared and belongs to the “dead zone”. If the default value of Y is x, no error will be reported because X has been declared.


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

In addition, the following code will also report an error, which is different from the behavior of var.

//No error reporting
var x = x;
//Report a mistake
let x = x;
// ReferenceError: x is not defined

The error in the above code is also due to the temporary dead zone. When using let to declare a variable, an error will be reported as long as the variable is used before the declaration is completed. The above line belongs to this case. Before the declaration statement of variable x is executed, the value of X is taken, resulting in an error “x undefined”.

ES6 stipulates that temporary dead zone and variable promotion are not allowed in let and const statements. The main purpose is to reduce runtime errors and prevent the use of this variable before variable declaration, resulting in unexpected behavior. Such errors are very common in Es5, and now that there are such rules, it’s easy to avoid them.

In a word, the essence of temporary dead zone is that as soon as it enters the current scope, the variable to be used already exists, but cannot be obtained. Only when the line of code that declares the variable appears, can the variable be acquired and used.

Duplicate declaration is not allowed

Let does not allow repeating declaration of the same variable within the same scope.

//Report a mistake
function () {
 let a = 10;
 var a = 1;
}
//Report a mistake
function () {
 let a = 10;
 let a = 1;
}

Therefore, parameters cannot be redeclared inside a function.

function func(arg) {
 Let Arg; // error reported
}
function func(arg) {
 {
  Let Arg; // no error is reported
 }
}

Block level scope

Why block level scope is needed?

Es5 has only global scope and function scope, but no block level scope, which brings many unreasonable scenarios.

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


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

The original meaning of the above code is that the outer TMP variable is used outside the if code block, and the inner TMP variable is used internally. However, after the function f is executed, the output result is undefined. The reason is that the variable promotion causes the inner TMP variable to cover the outer TMP variable.

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


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

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

Block level scope of ES6

Let actually adds a block level scope to JavaScript.


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

The above function has two code blocks, which declare variable n and output 5 after running. This means that the outer code block is not affected by the inner code block. If VaR is used to define variable n twice, the final output value is 10.

ES6 allows arbitrary nesting of block level scopes.


{{{{{let insane = 'Hello World'}}}}};

The code above uses a five tier block level scope. Outer scope cannot read variables of inner scope.

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

The inner scope can define variables with the same name in the outer scope.


{{{{
 let insane = 'Hello World';
 {let insane = 'Hello World'}
}}}};

The emergence of block level scope actually makes it unnecessary to obtain the widely used immediate execution function expression (Iife).

//Iife writing
(function () {
 var tmp = ...;
 ...
}());
//Block level scope writing
{
 let tmp = ...;
 ...
}

Block level scope and function declaration

Can functions be declared in block level scope? This is a rather confusing issue.

Es5 stipulates that functions can only be declared in the top-level scope and function scope, not in the block level scope.

//Situation one
if (true) {
 function f() {}
}
//Situation two
try {
 function f() {}
} catch(e) {
 // ...
}

The above two function declarations are illegal according to the provisions of Es5.

However, the browser does not comply with this rule. In order to be compatible with the old code, it still supports the declaration of functions in the scope of block level. Therefore, the above two situations can actually run without error.

ES6 introduces block level scope, which explicitly allows functions to be declared in block level scope. ES6 stipulates that in block level scope, the behavior of function declaration statement is similar to let and cannot be referenced outside block level scope.

function f() { console.log('I am outside!'); }
(function () {
 if (false) {
  //Repeat the declaration of function F
  function f() { console.log('I am inside!'); }
 }
 f();
}());

If the above code is run in Es5, it will get “I am inside!” because the function f declared in if will be promoted to the function header. The actual running code is as follows.

//Es5 environment
function f() { console.log('I am outside!'); }
(function () {
 function f() { console.log('I am inside!'); }
 if (false) {
 }
 f();
}());

ES6 is totally different. In theory, I am outside. Because functions declared within a block level scope are similar to let, they have no effect outside the scope. However, if you run the above code in ES6 browser, you will report an error. Why?

It turns out that if you change the processing rules of functions declared at the block level, it will obviously have a great impact on the old code. In order to reduce the incompatibility problem, ES6 stipulates in Appendix B that browser implementation can not comply with the above provisions and have its own behavior mode.

Allows functions to be declared at the block level scope

A function declaration is similar to VaR, which is promoted to the head of the global scope or function scope.

At the same time, the function declaration is promoted to the head of the block level scope it is in.

Note that the above three rules are only valid for the browser implementation of ES6. Implementation in other environments does not need to comply with them, and the function declaration of block level scope is treated as let.

According to these three rules, in the browser’s ES6 environment, functions declared at the block level behave like variables declared by var.

//ES6 environment of browser
function f() { console.log('I am outside!'); }
(function () {
 if (false) {
  //Repeat the declaration of function F
  function f() { console.log('I am inside!'); }
 }
 f();
}());
// Uncaught TypeError: f is not a function

The above code will report an error in ES6 compliant browsers, because the actual running code is the following code.

//ES6 environment of browser
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 function

Considering that the behavior differences caused by the environment are too large, you should avoid declaring functions at the block level. If necessary, it should also be written as a function expression, not a function declaration statement.

//Function declaration statement
{
 let a = 'secret';
 function f() {
  return a;
 }
}
//Function expression
{
 let a = 'secret';
 let f = function () {
  return a;
 };
}

In addition, there is another thing to pay attention to. The block level scope of ES6 allows the rule to declare functions, which only holds when braces are used. If no braces are used, an error will be reported.

//No error reporting
'use strict';
if (true) {
 function f() {}
}
//Report a mistake
'use strict';
if (true)
 function f() {}

Do expression

In essence, a block level scope is a statement that encapsulates multiple operations with no return value.


 {
 let t = f();
 t = t * t + 1;
}

In the above code, the block level scope encapsulates the two statements together. However, outside the block level scope, there is no way to get the value of T, because the block level scope does not return a value unless t is a global variable.

Now there is a proposal to make the block level scope become an expression, that is to say, it can return a value by adding do before the block level scope to make it a do expression.


let x = do {
 let t = f();
 t * t + 1;
};

In the above code, variable x gets the return value of the entire block level scope.

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.1415
PI = 3;
// TypeError: Assignment to constant variable.

The above code indicates that changing the value of a constant will result in an error.

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

const foo;
// SyntaxError: Missing initializer in const declaration
The above code indicates that for const, if you just declare no assignment, an error will be reported.
Const has the same scope as the let command: it is only valid within the block level scope in which the declaration is located.
if (true) {
 const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined

Constants declared by const command are not promoted. There is also a temporary dead zone, which can only be used after the declared position.


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

The above code is called before the constant Max is declared, and an error is reported.

Const declared constants are also non repeatable like let.

var message = "Hello!";
let age = 25;
//Both of the following lines will report errors
const message = "Goodbye!";
const age = 30;

essence

What const actually guarantees is not that the value of a variable cannot be changed, but that the memory address that the variable points to cannot be changed. For simple types of data (numeric, string, Boolean), the value is stored at the memory address that the variable points to, so it is equivalent to a constant. However, for compound type data (mainly objects and arrays), the memory address pointed to by the variable only stores a pointer. Const can only guarantee that the pointer is fixed. As for whether the data structure it points to is variable, it can not be controlled at all. Therefore, you must be very careful about declaring an object as a constant.

const foo = {};
//Add an attribute to foo, you can succeed
foo.prop = 123;
foo.prop // 123
//If foo is pointed to another object, an error is reported
foo = {}; // TypeError: "foo" is read-only

In the code above, the constant foo stores an address that points to an object. The only immutable thing is that the foo cannot be pointed to another address, but the object itself is mutable, so you can still add new properties to it.

Here’s another example.

const a = [];
a. Push ('Hello '); // executable
a. Length = 0; // executable
A = ['dave ']; // error reported

In the above code, the constant a is an array. The array itself is writable. However, if another array is assigned to a, an error will be reported.

If you really want to freeze objects, you should use Object.freeze method.

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

In the above code, the constant foo points to a frozen object, so adding new properties does not work, and an error will be 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 objects completely.


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

Six ways to declare variables in ES6

Es5 has only two ways to declare variables: the VaR command and the function command. In addition to the let and const commands in ES6, there are two other ways to declare variables: the Import command and the class command. So, ES6 has six ways to declare variables.

Properties of the top-level object

Top level object refers to window object in browser environment and global object in node. In Es5, the attributes of top-level objects are equivalent to global variables.


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

In the above code, the property assignment of the top-level object and the assignment of the global variable are the same thing.

Linking the properties of top-level objects with global variables is considered one of the biggest design failures of JavaScript language. This design has brought several big problems. First, it is impossible to report the undeclared errors of variables at compile time, which can only be known by the runtime (because global variables may be created by the properties of top-level objects, while the creation of attributes is dynamic); secondly, programmers can easily create global variables unconsciously (such as typing errors); finally, the top-level control system can not report the undeclared errors of variables at compile time The properties of images can be read and written everywhere, which is not conducive to modular programming. On the other hand, window object has entity meaning, which refers to the window object of browser. The top object is an object with entity meaning, which is also inappropriate.

In order to change this, on the one hand, in order to maintain compatibility, the global variables declared by the VaR command and function command are still the attributes of the top-level object; on the other hand, the global variables declared by the let command, const command and class command do not belong to the properties of the top-level object. In other words, starting with ES6, global variables will gradually be decoupled from the properties of the top-level objects.

var a = 1;
//In the repl environment of node, it can be written as global. A
//Or write this. A in a general way
window.a // 1
let b = 1;
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; the global variable B is declared by the let command, so it is not a property of the top-level object, and returns undefined.

Global object

The top-level object of Es5 itself is a problem, because it is not unified in various implementations.

In the browser, the top-level object is window, but node and web worker have no window.

In browsers and web workers, self also points to top-level objects, but node does not have self.

In node, the top-level object is global, but other environments do not support it.

In order to get the top-level object in all kinds of environments, the same code generally uses this variable now, but there are limitations.

In the global environment, this returns the top-level object. However, in node module and ES6 module, this returns the current module.

If the function is not run as an object method, but simply as a function, this will point to the top-level object. However, in strict mode, this will return undefined.

Whether in strict mode or normal mode, new function (‘return this’) () always returns global objects. However, if the browser uses CSP (content security policy), Eval and new function may not work.

To sum up, it is difficult to find a way to get the top-level object in all cases. Here are two ways that you can barely use.

//Method 1
(typeof window !== 'undefined'
  ? window
  : (typeof process === 'object' &&
   typeof require === 'function' &&
   typeof global === 'object')
   ? global
   : this);
//Method 2
var getGlobal = function () {
 if (typeof self !== 'undefined') { return self; }
 if (typeof window !== 'undefined') { return window; }
 if (typeof global !== 'undefined') { return global; }
 throw new Error('unable to locate global object');
};

Now there is a proposal to introduce global as the top-level object at the level of language standards. In other words, global exists in all environments, and top-level objects can be obtained from it. Gasket storehouse system.global After simulating this proposal, we can get global in all environments.

//How to write commonjs
require('system.global/shim')();
//Writing method of ES6 module
import shim from 'system.global/shim'; shim();

The above code can ensure that global objects exist in various environments.

//How to write commonjs
var global = require('system.global')();
//Writing method of ES6 module
import getGlobal from 'system.global';
const global = getGlobal();

The code above puts the top-level object into the variable global.

For more information about wechat applet development and JavaScript ES6 Standard Specification, please see the following related articles