1. Introduction

Recently read some information on the JS execution stack has a new experience, have a plenty of from some books (the little red book, “you don’t know the JS”), have a plenty of some excellent blog and tutorials, my mind a lot of things, but common development not to apply these knowledge visually, so wanted to think, or write a blog to record it, Let’s find out why

2. Code execution flow in JS

I’ll start with a question: how does JS code actually execute?

We all know that JS is single-threaded, but strictly speaking the program responsible for executing JS code is single-threaded, and that program is the JS engine, as we know Google V8. Execution of JS code requires the environment to provide memory space, dependent global variables, and an Event Loop system, which is provided by the host environment of the JS engine, either a browser or Node.js (the discussion below is based on the V8 environment in the browser).

When we’re done writing code that still looks like a string (as opposed to the code itself), we go through the following phases:

  • Lexical analysis: Parsing code strings into lexical units (tokens), which you can interpret as parts of speech (noun, verb, preposition…) in English words
  • Parsing: Parsing lexical unit streams into AST (Abstract syntax tree). This process includes lexical scope generation, variable promotion, and so on.
  • Code generation: Convert to bytecode based on the AST generated above, which is part of V8IgnitionGenerated by the interpreter.
  • Execution code: Interprets execution of bytecodes one bytecode at a time, when V8 finds a large number of duplicate bytecodes (hot code)HotSpot), which compiles it to machine code (from the engineTurboFanThe next time it encounters similar bytecodes, it is executed without explanation. This process in conjunction with the interpreter is also calledJIT(Real-time compilation).

The bottom line is how the CPU executes computer instructions, which is beyond the scope of this article. The above process is represented by a graph:

With the above knowledge, we can know that JS execution is generally divided into two stages:

  • Compilation stage: variable promotion, determination of lexical scope.
  • Run phase: Create execution context, scope chain, and so on.

You don’t have to see the right side of the picture, you just need to know what it looks like.

The topic of this article will be explained in terms of the compile phase and the run phase.

2.1 Executable Environment

How does the engine know to execute code? There are three main types of executable environments in JS:

  • Global execution environment
  • Function execution environment
  • Eval Execution environment

The context in which Eval is executed is ignored for the moment.

Let’s start with the global execution environment. When you open a TAB page, the browser assigns a separate rendering process (regardless of iframe and local TAB) that provides everything the JS engine needs (memory, global variables), including the global execution environment. This means that as soon as you open the TAB page, the JS engine starts working, and it will execute the code inside the script tag.

When a function call is encountered, a new execution environment is created, which is the function execution environment, also known as the execution context. In order to manage the function call relationship, JS applies a fixed memory (different browser size varies), this memory area is the execution environment stack, it manages all the function call relationship in the current rendering process. When the TAB page opens, a global execution context is created and placed at the bottom of the stack.

Let’s start with a few ideas:

  • Execution Environment: An Environment in which JS code can be executed.
  • Execution Stack Context: An area of memory used to manage the calling relationships of functions.
  • Execution Context: The Context of the block in which the current code resides, holding data (such as this, variables) required by the current code.

Let’s look at a simple example:

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

The above code looks like this:

Of course, the browser can also see the call relationship clearly:

2.2 Variable Objects

Now there is a new question: how does the a variable of the bar function find its value? Log is undefined. Where is it?

Here I quote from the Little Red Book:

The context of variables or functions determines what data they can access and how they behave. Each context has an associated variable object on which all variables and functions defined in that context reside.

This variable object exists in the execution context, and the global execution context is special. It also has a global object, which contains various apis that can be provided to the JS engine: DOM, BOM, timer… But we can’t access it directly, we have to access it through a reference relationship, which is the familiar Window object, and for our convenience, we can omit the window keyword when using these apis.

After talking about variable objects in the global execution context, let’s talk about the function execution context. It also has a variable object inside, but it has a special name. The variable object in the function is called an activation object. Why should I specialise when both objects are the same? When I think about it, the function execution context is activated only when the function is called, and the various variables in the function are “active” during the activation process. If the function does not use the code in the function, it is normal string for the interpreter, so it is good to call the active object.

At this point, let’s summarize again:

In the execution context there is a guy called a variable object, which is called a variable object in the global execution context, plus a global object, which is called a activation Object in the function execution context. They contain all kinds of data that your code needs to execute.

So far, we’ve touched on a lot of nouns, but for the sake of drawing later, here are some abbreviations:

  • AO: Activation Object.
  • GO: Global Object.
  • VO: Variable object.
  • ESC: Executes the environment stack.
  • EC: execution environment.

So when does a variable go into a variable object? This is where variable promotion comes in.

2.3 Variable Promotion

Let’s look at another example:

console.log(a)

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

The last function, a, is printed as a string because the code promotes variables during AST generation and stores them in VO/AO with an initial value of undefined. If the variable declaration already exists in VO/AO, skip it. Override if it is a variable of the same name declared by function.

Variables can be promoted in the following ways:

  • Function Specifies the declared variable.
  • Var Specifies the declared variable.
  • Import declared variables (temporarily ignored).

The whole code above will scan the above mentioned keywords (function and var in this case) before executing, if any will be promoted to VO, here is the overall process of parsing:

  • Encountering a function declarationfunction aAnd in theVO, and let its initial value beundefined.
  • encountervar aReady to put inVOAnd it turns out thatVOA variable with the same name already exists, skip it.
  • To meet againfunction aReady to put inVOAnd it turns out thatVOVariable with the same name already exists, override.

It can be seen from this that the function declaration has a lot of “weight”. The same variable can be overwritten by the function declaration, indicating that the function declaration has a higher priority than the var declaration.

It is also important to note that variable promotion is only promoted to the top of the current lexical scope. What is lexical scope?

2.4 Lexical scope

As we said at the beginning, the lexical scope is defined when the code generates the AST. This means that the lexical scope is defined before the code executes its lexical scope. In general, the lexical scope is the area that a variable can be accessed and determines the lifetime of the variable. Let’s look at another piece of code:

function baz() {
    console.log(a)
}

function foo() {
    var a = 2
    function bar() {
        var a = 3
        console.log(a)
        baz()
    }
    bar()
}

var a = 1

foo()
Copy the code

Remember that the lexical scope of a function depends on where it is defined. The execution stack of the above code is:

  • The foo function is called, a new execution context is created, and pushed.
  • The bar function is called, creating a new execution context and pushing it onto the stack.
  • The baz function is called, creating a new execution context and pushing it onto the stack.

So how does baz know which lexical scope to go to when it accesses a variable?

A special mechanism needs to be introduced here: Function when creating AO will store it at the next higher level of lexical scope references, when can’t find a baz execution context, through this reference to up a layer of lexical scope of the search, and it is a layer of lexical scope on the global scope, so it eventually print is 1, the process is also the scope chain mechanism, The process of finding the value of A is also an RHS query rule.

A scope is a set of rules for determining where and how to look for variables (identifiers). If the purpose of the lookup is to assign a value to a variable, an LHS query is used; If the goal is to get the value of a variable, RHS queries are used. — JavaScript you Don’t Know (part 1).

3. Case study

In the previous section, we started at the execution stage of the code and traced back to the compile stage to see how JS is handled differently at different stages. Next, we will combine some cases to talk about all the knowledge points involved in this paper.

3.1 a case

var a = {
    n: 1,}var b = a

a.x = a = {
    n: 2,}console.log(a.x)
console.log(b)
Copy the code

This is a classic problem, so let’s analyze it. First at compile time:

  • encountervarDo variable promotion.
  • Determine the global scope.
  • Save external (context) references inouter(Here’s the big picture).

When executing code:

  • Dynamically allocate heap area memory
  • Determine the execution context

To illustrate the process:

3.2 second case

var num = 100
function fn() {
    var num = 200
    return function (a) {
        console.log(a + num++)
    }
}

var foo = fn()
foo(10)
foo(20)
Copy the code

Same analysis as above:

  • Variable promotion: Num, fn, foo promoted to VO, initial valueundefined.
  • A function is also a special object, so it also allocates space in the heap.
  • Verify function context when calling a function (this,AO…). , scope chain, parameter assignment, initializationarguments.

When fn is called, it creates a function execution context, pushes it onto the stack, and stores the returned function reference value in the foo variable. However, when FN is called off the stack, it finds that the internal function references the NUM variable in the AO, and the address cannot be reclaimed, thus creating a closure.

Then call foo, create a new context, set the argument to 10, and use an RHS query to find num in the closure, so that it prints 210 and increases the num in the closure to 201. The next time foo is called, the process is the same, so I won’t analyze it here.

How do closures get destroyed?

The closure can only be released when the page is closed (or manually released by setting foo=null). If foo is a local variable, When the function is destroyed, it waits for the garbage collector to collect it.

The manual release mentioned above is to free the memory immediately, but to leave it unreferenced until the garbage collector collects it. This is not the same as the C++/C free memory concept.

4. To summarize

This article mainly on JS execution process involved in some such as execution context, variable object, variable promotion and other features are explained, and finally with two cases to carry on an overview of the above process. Although these are some conceptual knowledge, but understanding these concepts to our understanding of THE JS language has a great help. Finally, this article inevitably has some concepts did not elaborate the wrong situation, please criticize and correct, your suggestion is also to my affirmation, thank you for watching ~

Reference 5.

  • “How Browsers Work and Practice” (Geek Time)
  • JavaScript You Don’t Know (Part 1)
  • JavaScript Advanced Programming (4th edition)
  • How Js works (recommended reading)