Var declaration and variable promotion mechanism

When writing JS code, many people use the var keyword to declare variables. The variable declared by the var keyword makes a variable declared anywhere in the function scope or global scope treated as if it were declared at the top of the current scope. This is known as the antibiotically reactive mechanism. Here’s an example:

functionExample (flag) {console.log(value) // The variable value can be accessed, and the value is undefinedif (flag) {
    var value = 'hello'Console. log(value) // The variable value that can be accessed here is hello}else{console.log(value) // The value can be accessed here, the value is undefined}}Copy the code

People who don’t know js might think that the value variable can only be accessed if flag is true, but the value variable declared by var has been pushed to the top of the scope of the example function, so that the value variable can be accessed anywhere in the function. The value is initialized only if flag is true and value. During the precompilation phase, the js engine will parse the example function as follows:

functionExample (flag) {var value console.log(value) // The variable value can be accessed, and the value is undefinedif (flag) {
    var value = 'hello'Console. log(value) // The variable value that can be accessed here is hello}else{console.log(value) // The value can be accessed here, the value is undefined}}Copy the code

Developers who are new to JS usually take a while to get used to variable promotions, and sometimes misunderstandings lead to bugs in their applications. For this reason, block-level scope is introduced in ES6 to strengthen the control of variable life cycle.

Block-level statement

Block-level declarations are usually used when we need a variable that can only be accessed in a given block. Block-level scopes (also known as lexical scopes) exist in:

  • Function of the internal
  • Block (area between ‘{}’)

Let the statement

The var of the let declaration has the same declaration syntax. Variables declared by let can only be accessed in the current scope, and let-declared variables are not promoted, so developers usually place let-declared variables at the top of the scope so that the whole scope can access them. Here is an example of a let declaration:

functionExample (flag) {console.log(value) // There is no value hereif(flag) {console.log(value) // There is no value variable herelet value = 'hello'Console. log(value) // The variable value that can be accessed here is hello}else{console.log(value) // There is no value here}}Copy the code

The variable value declared by the let in the example is not promoted to the top, the execution stream leaves the if block and the value is destroyed immediately. If flag is false then the value variable will never be declared and initialized.

Prohibition of redeclaration

If the let keyword declares a duplicate identifier, an error will be thrown, as in:

Var count = 30 // Throw syntax errorlet count = 40
Copy the code

In this example, as mentioned above, let cannot duplicate an identifier that already exists, so the let declaration here will report an error. But it can be accessed normally if the current scope of the count variable declared by the let is embedded in another scope, for example:

var count = 30
if(flag) {// No syntax errors are thrownlet count = 40
}
Copy the code

The count declared by the let here is only accessible in the scoped if block, and the count declared by the let in the if block overrides the count variable declared by the var outside

Const statement

Const is the key used to declare constants. Its value cannot be changed once determined, so every constant declared through const must be initialized.

// Valid const maxItems = 30 // syntax error: constant uninitialized const name;Copy the code

Const and let

Both const and let declare block-level identifiers, so const constants are only accessible in the current scope and are not promoted to the top. Likewise, const cannot declare a duplicate identifier, whether that identifier is declared using var (in global or function scope) or let (in block-level scope). For example:

var maxItems = 30
let name = 'coco'// Both statements throw errors const maxItems = 60 const name ='koko'
Copy the code

While there are many similarities between const and let, one major difference is that const constants cannot be assigned again. Constants in ES6 are similar to other languages, however, unlike other languages, if a constant in JS is an object, the value in the object can be modified.

Declare an object with const

A const declaration does not allow binding modification, but does allow value modification, which means that an object with a const life can modify its property values. Here’s an example:

const dog = {
  name: 'koko'} // You can modify the object property value dog.name ='coco'// throw a syntax error dog = {name:'coco'
  }
Copy the code

Changing the dog property in this code does not report an error, because you are changing the value that dog contains, and the binding for dog has not changed. An error is reported if the binding of dog is changed

Temporal Dead Zone

Let and const, unlike var, can trigger reference errors if they are accessed before they are declared by let or const, even relatively safe Typeof operations, such as this code:

if(flag) { console.log(typeof (value)); // Reference errorlet value = 'b'
}
Copy the code

Because console.log(typeof (value)) throws an error, the declaration and initialization of the value will not be performed. The value is still in what the JS community calls a “temporary dead zone” (TDZ), which is commonly used to describe the unpromoted effects of lets and const. (Let is used here, but so is const). When the JS engine scans the code for variable declarations, it either pushes them to the top of scope (encountering var declarations) or puts them into TDZ (encountering let and const declarations). Accessing variables in TDZ triggers a runtime error. Variables are removed from TDZ only after the variable declaration statement has been executed before they can be accessed normally. As you can see from the above example, even a non-error-prone Typeof does not prevent the engine from throwing errors, but using typeof on this variable outside the scope of the let declaration does not report an error, as in:

console.log(typeof (value)); //"undefined"
if (flag) {
  let value = 'b'
}
Copy the code

Typeof is executed outside of the declaration variable value block, which is not in TDZ at this point. This means that there is no value binding, and typeof eventually returns’ undefined ‘. TDZ is just one feature of block-level binding, as is the use of block-level binding in loops.

Block scoped bindings in loops

Developers should be most interested in implementing block-level scopes in the for loop because they can limit arbitrarily declared counter variables to use in the for loop. Code like the following is common in JS:

for(var i = 0; i < 10; i++) { process(items[i]); } console.log(I) // The variable I is still accessible hereCopy the code

In the example code, since the counter variable I is declared by var, variable I is promoted to the top of the scope outside the for loop, so variable I can be accessed even outside the loop, but using the let declaration instead achieves the desired effect:

for (leti = 0; i < 10; i++) { process(items[i]); } console.log(I) // Cannot access variable I here, throws an errorCopy the code

In the above example, the variable I declared by let is only accessible in the for loop. The above two examples work fine in other languages (with block-level scope by default), but the variable I is always accessible only in the for loop.

Function in loop

Using var declarations has long made it difficult for developers to create functions in loops because the variables in each loop are still accessible to the next one. Such as:

var funArr = [] 
for (var i = 0; i < 10; i++) {
  funArr.push(function () {
    console.log(i)
  })
}
funArr.forEach(function(func) {func() // output 10 times the number 10})Copy the code

In the example above, you might want to print 0-9, but instead he prints 10 10s in a row. This is because each iteration in the loop shares variable I at the same time, and the functions created inside the loop all retain references to the same variable (savor it). At the end of the loop, I was given a value of 10, so all the functions created internally in the loop referenced variable I with a value of 10, and each call to console.log(I) printed the number 10. To do this, the developers recycle using immediate call function expressions (IIFE) to force a copy of the counter variable, like this:

var funArr = [] 
for (var i = 0; i < 10; i++) {
  funArr.push((function (value) {
    return function () {
      console.log(value)  
    }
  }(i)))
}
funArr.forEach(function(func) {func() // 0-9})Copy the code

In the above example, the IIFE expression generates a copy of the variable I in each loop (the parameter value variable). The variable value replaces the variable I referenced in the function created in each loop, and the value is the value of I in the current loop. The value generated in each loop is accessible only in the current loop. So calling the function produces the expected 0-9. The block-level binding provided by lets and const in ES6 makes this unnecessary.

Let declaration in loop

The let declaration is similar to what IIFE did above, in that each iteration creates a new variable and initializes it with the value of the variable of the same name from the previous iteration. But the code is relatively clean. Code examples:

var funArr = [] 
for (let i = 0; i < 10; i++) {
  funArr.push(function () {
    console.log(i)
  })
}
funArr.forEach(function(func) {func() // 0-9})Copy the code

Each loop let declaration in the above code creates a new variable I and initializes it to the current value of I, so functions created in each loop get their own copy of I. The same is true for for-in and for-of loops as shown in the following example:

var funArr = [], 
object = {
  a: true,
  b: true,
  c: true
};
for (let key in object) {
  funArr.push(function () {
    console.log(key)
  })
}
funArr.forEach(function(func) {func() // a,b,c})Copy the code

The for-in loop behaves the same as the for-loop in the previous example, assigning a new variable (key in the previous example) to each function created in the loop. But these functions will all print ‘c’ if var is used.

It is important to understand that the behavior of let declarations within loops is specifically defined in the standard and is not necessarily related to the unpromoted nature of LET. In fact, earlier let implementations did not include this behavior; it was added later.

Const declaration in a loop

There is nothing in ES6 that explicitly disallows const declarations in loops. Using const declarations in different types of loops behaves differently. A normal for loop can initialize a variable using const, but will report an error whenever the value of the variable changes, like this:

Var funArr = [] // Throw an error after an iterationfor (const i = 0; i < 10; i++) {
  funArr.push(function () {
    console.log(i)
  })
}
Copy the code

In the above example, the constant I declared by const, on the first iteration of the loop, I =0 succeeds, and then I ++ attempts to change the value of constant I, so an error is thrown. Therefore, a const declaration is appropriate for loops in which the variable is not modified by subsequent loops. Using const in for-in and for-of loops behaves the same as using let. The following code should not generate an error:

var funArr = [], 
object = {
  a: true,
  b: true,
  c: true}; // No errors are generatedfor (const key in object) {
  funArr.push(function () {
    console.log(key)
  })
}
funArr.forEach(function(func) {func() // a, b, c})Copy the code

This code is almost identical to the previous code, except that the key cannot be changed within the loop. As mentioned earlier, a constant declared by const may not modify its binding, but it may modify the binding value of the constant (the property of the object declared by const). This works in for-in and for-of loops because each iteration does not (as in the previous for loop example) modify the existing binding, but instead creates a new binding.

Global block-scoped binding

Another difference between lets and const and VAR is their behavior in the global scope. When var is used in the global scope, it creates a new variable as a property of the global object (which is usually the Window object in the browser environment). This means that var could inadvertently overwrite an existing global property, like this:

// Create the global property RegExp for the global object window in the browser"hello"; console.log(window.RegExp); // "hello" // The global attribute RegExp is overwritten var RegExp ="hi"console.log(window.RegExp); / / "hi"Copy the code

Even if the global variable RegExp is defined on the global object Window, it is not immune to being overridden by var declarations. The global variable RegExp declared in the example overrides an existing global property. Js has always been like this. If you use let or const declarations in the global scope, they create a new binding in the global scope, but that binding is not added as a property of the global object. In other words, let and const do not overwrite the global variable, only it. The following is an example:

let RegExp = "hello"; console.log(RegExp); Log (window.RegExp === RegExp) //false
const ncz = "hi"; console.log(ncz); / / "hi" console. The log ("ncz" in window) //false
Copy the code

The RegExp declared by the let here creates a binding and obscures the variables of the global RegExp. The result is that window.RegExp is not the same as RegExp, but does not break the global scope. The same is true for const. It’s much safer to use let and const declarations if you don’t want to create properties for global objects.

If you want to define variables in a global object, you can still use var. This is often the case when accessing code across frames or Windows.

Evolution of block-level binding best practices

The default is const, and let is used when you need to change the value of a variable. Most variables should never change after initialization, and unexpected variable value changes are the source of many bugs.