JS engine

Javscript cannot be executed directly by a computer. It needs to be compiled into machine code by the JS engine.

In V8, this is divided into three phases

  • From source code to abstract syntax trees
  • From abstract syntax trees to bytecodes
  • From bytecode to machine code

v8 wiki

V8 first generates an abstract syntax tree with its own parser. Then, Ignition generates bytecode from this syntax tree using the internal V8 bytecode format. TurboFan compiles this bytecode into machine code.

Let’s use a picture to make it more intuitive:

  • AST

AST Explorer: In this site, we can see the structure of the AST

There are two stages from AST to Machine Code:

  • Your desk is out of Interpreter.
  • Complier
// complier vs Interpreter
function someCalculation(x, y) {
  return x + y;
}

// Interpreter interprets the compilation execution line by line. Compile fast, run slow
for (let i = 0; i < 1000; i++) {
  someCalculation(4.5);
}

// run complier after optimization. Compile slowly, run fast
for (let i = 0; i < 1000; i++) {
  9;
}
Copy the code

Modern browsers use just-in-time compilation (JIT) :

Just-in-time compilation (English: Just-in-time compilation, abbreviated JIT; Is a method of executing computer code that involves compiling during program execution (at run time) rather than before execution. Typically, this involves source code or, more commonly, bytecode to machine code conversion, which is then performed directly. Systems implementing JIT compilers typically constantly analyze the code being executed and identify parts of the code where the acceleration gained from compilation or recompilation would outweigh the overhead of compiling the code.

JIT compilation is a combination of two traditional machine code translation methods, AOT and explain, combining the strengths and weaknesses of both.

How is JS executed

Now that we have some understanding of how the JS engine works, let’s get back to JS itself. How does JS execute?

There are a couple of important concepts here

  • Execution context
  • scope
  • closure

Execution context

Recommended blog: JavaScript Execution Context

When the JS engine executes a JS script (or calls a function), it creates an execution context. Each execution context has two phases: the creation phase and the execution phase.

  • Create a stage

    • Create a Global object: Global (window in browser, Global in Node)
    • Create this object that points to Global(in the context of a function call, this refers to the object on which the function was called)
    • Set up memory pairs to hold variable and function references
    • Initialize function declaration, variable initial value is undefind
  • The execution phase is executed line by line. This includes assignment operations and function calls

The execution context is destroyed when the function exits.

Arrow functions do not have the this binding and arguments. Because it is an assignment operation, the execution phase is defined.

Scope and scope chain

During the creation phase of the execution context, the scope chain is created after the variable object. The scope chain itself contains variable objects.

Scope chains are used to resolve variables. When asked to parse a variable, JavaScript always starts at the innermost layer of code nesting and keeps jumping back to the parent scope until the variable is found.

Lexical scope

A simple explanation: functions can be called from anywhere, but variable lookup within a function depends on the scope in which the function is defined.

function outerFunc() {
  // the outer scope
  let outerVar = "I am from outside!";
  function innerFunc() {
    // the inner scope
    console.log(outerVar); // 'I am from outside! '
  }
  return innerFunc;
}
const inner = outerFunc();
inner();
Copy the code

closure

let count = 10;
function outer() {
  let other = "other";
  let count = 0;
  return function () {
    count++;
  };
}

const fn = outer();

fn();
fn();
Copy the code

At chrome breakpoint debugging, you can see that fn has a [[scopes]] property [Closure, Script, Global]

My understanding is:

The execution context is destroyed after the function is pushed out of the call stack.

If the function returns a function (or an object with an attribute value that is an internal function)

The return function is assigned to the external variable

This return function keeps variable references to external functions

These variables are not destroyed, but stored in closures

conclusion

These concepts are important and obscure, and more complex to use in real-world scenarios. So first to understand its mechanism, more exposure to some practice, in the encounter where there is doubt breakpoint debugging, slowly will have more understanding of it.

Personal blog