preface

JavaScript is known to be a single-threaded language. So JavaScript executes in order! End of article (Dog head)

Compile before you execute

Variable ascension

Look at the following example:

console.log(cat)
catName("Chloe");
var cat = 'Chloe'
function catName(name) {
    console.log("My cat's name" + name);
}
Copy the code

Following the conclusion that “JavaScript is executed sequentially “, the steps are as follows:

  • When the first sentence is executed, cat is not defined, and the result should be an error thrown and execution terminated.
Uncaught ReferenceError: cat is not defined
Copy the code

But the actual result is not:

Not only can it be executed, but the catName() result is also printed.

This phenomenon is called variable promotion

In the literal sense of the term, “variable promotion” means that the declaration of variables and functions is moved to the front of the code. When a variable is promoted, it is given the default value of undefined.

The execution sequence after adjustment is as follows:

  • Var cat = undefined and function catName(){}
  • Log console.log(cat) // undefined
  • Then call catName()
  • And then I’ll give cat = ‘Chloe’

The word ‘move’ is misleading. The actual location of the code at the physical level has not changed. JavaScript is a language that parses execution and goes through a compile phase before execution. The reason for this is that the JavaScript engine puts declarations of variables and functions in memory at compile time.

Execution context

Variable promotion (as of 1997) is considered an understanding of how execution contexts (especially the creation and execution phases) work in Javascript

At compile time, JavaScript creates an execution context and executable code for the above code.

An execution context is the context in which JavaScript executes a piece of code, including this, variables, objects, and functions.

1. During compilation

  • JavaScript engines place variable promotion content such as var variable declarations and function declarations in the variable environment.
  • The JavaScript engine then compiles the non-declared code into bytecode — executable code.

2. Execution phase

  • When console.log(cat) is executed, the JavaScript engine looks for the cat variable in the variable environment, and since the variable environment contains the cat variable and its value is undefined, it prints undefined.
  • When the catName function is executed, the engine looks for the function in the variable environment, and since a reference to the function exists in the variable environment, the engine executes the function and prints the execution result.
  • Cat assignment is performed. The engine finds the CAT variable in the variable environment and performs the assignment.

Global execution context: When the JS engine compiles global code, it creates a global execution context. There is only one global execution context on the current page.

2. Function execution context: When a function is called, the JS engine creates a function execution context. Normally, the function execution context is destroyed when the function is finished executing.

Eval execution context: When eval is executed, an execution context is also created.

The call stack

The JS engine manages multiple execution contexts through the data structure of the stack.

A stack is an abstract data type in computer science that allows adding data (push) and removing data (POP) operations only at one end of an ordered linear set of data (called the top of the stack). So it works on a LIFO (Last In First Out) principle

After an execution context is created, the JS engine pushes it onto the stack. The stack structure that manages the execution context is called the call stack, or execution context stack.

Look at the following example:

function foo() {
    var a = 0
    console.log(a)
}
function bar() {
    var b = 1
    foo()
    console.log(b)
}
bar()
Copy the code

The steps are as follows:

Create the global execution context and push it to the bottom of the stack.

2. Execute the global code: bar(). When the bar function is called, the JS engine compiles the bar function and creates a function execution context for it. Finally, the execution context is pushed onto the stack and variable B is given the default value of undefined.

Execute the code inside the bar function. The assignment b = 1 is performed, and then foo is called. The JS engine compiles the foo function and creates a function execution context for it. Finally, the execution context is pushed onto the stack and variable A is given the default value of undefined.

4. Execute code inside Foo. Perform the a = 1 assignment and print the value of a. When foo completes execution, the call stack pops its execution context off the top of the stack. The bar function is then executed.

5. After executing the bar function, the call stack pops its execution context from the top of the stack. The global execution context remains

This is the end of the JavaScript process execution.

The call stack is a mechanism for the JS engine to track function execution. When multiple functions are called at a time, the call stack can track which function is being executed and the call relationship between each function.

Var defects and block-level scope

Problems with variable promotion

1. Variables are overwritten

var cat = "foo"
function catName(){
  console.log(cat);
  if(false){
   var cat = "bar"
  }
  console.log(cat);
}
catName()
Copy the code

When catName is called, the call stack is as follows:

  • When the catName execution context is created, the JavaScript engine places the var variable declaration cat promotion content in the variable environment, giving the default value undefined.
  • Log (cat) inside catName finds the value of cat in the variable context of catName execution, and prints undefined.
  • If the value is false, the command is not executed.
  • Execute console.log(cat), as in step 2, and print undefined.

2. Variables are not destroyed

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

Intuitively, you’d think that when the for loop ends, the I gets destroyed. The result is not so, console.log(I) prints 10.

The reason is that the variable is promoted, and I is promoted when the foo execution context is created. So when the for loop ends, the I is not destroyed.

Block-level scope

Storing a value in a variable and accessing or modifying that value is a basic function of a programming language. Scopes are rules about how variables are stored and how they are accessed.

Prior to ES6, JavaScript supported only two ways to create scopes:

  • Global scope
  • Function scope

Other programming languages generally support block-level scopes.

A block-level scope is a piece of code wrapped in braces, such as a function, a judgment statement, a loop statement, or even a single {} can be considered a block-level scope.

Simply put, variables defined inside the block-level scope are not accessible outside the block-level scope and are destroyed when the internal code completes execution.

Because JavaScript does not support block-level scope, there are problems with variable promotion.

Fortunately, ES6 has changed that, introducing new let and const keywords that provide an alternative to var for variable declarations.

The let and const keywords bind variables to any scope they are in (usually inside {}). In other words, let creates block scope for the variables it declares.

See the following example for a block-level scope:

var cat = "foo"
function catName() {if(true){
   var cat = "bar"
   console.log(cat);
  }
  console.log(cat);
}
catName()
Copy the code

In this code, the cat variable is declared in two places, one in the global scope and one in the if statement in the catName function scope.

When executing the inside of an if statement, the call stack looks like this:

As you can see from the figure, both console.log(cat) outputs bar.

Rewrite the above code using let

var cat = "foo"
function catName() {if(true) {let cat = "bar"
   console.log(cat);
  }
  console.log(cat);
}
catName()
Copy the code

At the end of the if statement, the cat variable declared by the let is destroyed and foo is printed in the second console.log(cat)

JavaScript implements block-level scope internally

Take a look at the following example

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()
Copy the code

The steps are as follows:

Step 1: Create the global execution context

2. Execute foo() to create an execution context for foo

  • Variables declared inside functions using var are placed in the variable environment and given a default value of undefined.
  • Variables declared using let inside functions are placed in the lexical context without a default value.
  • Variables declared using let inside {} inside functions are not placed in lexical context.

3. Execute the {} block inside foo, at which point a and B have been initialized, and enter the scope block. Variables declared by let in the scope block are stored in a separate area of the lexical environment, which does not affect variables outside the scope block.

A stack structure is maintained inside the lexical environment. The bottom of the stack is the outermost variable of the function. After entering a scoped block, the variables inside the scoped block will be pushed onto the stack. When scoped execution is complete, variables declared by lets and const of that scope are popped from the top of the stack.

4. When the scoped block finishes executing, the lexical environment’s stack structure pops its information off the top of the stack.

Variables declared using let or const are not accessible until they reach the declaration, and attempts to access them result in a reference error, even in normally safe operations (such as using typeof operators). The following is an example:

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

Because value is in the area of the temporal Dead Zone (TDZ) — a name that is not explicitly named in the ECMAScript specification, but is often used to describe why variables declared by lets or const cannot be accessed until they are declared.

conclusion

JavaScript code is compiled before it is executed.

2. Execution is performed sequentially, paragraph by paragraph, and a piece of code refers to an execution context.

There are three cases of execution context:

  • Global execution context
  • Function execution context
  • Eval Execution context

4. Let and const support block-level scope

At the end

For more articles, please go to Github, if you like, please click star, which is also a kind of encouragement to the author.