preface

This article is based on the author’s own understanding of the separation of THE JS operating mechanism, limited insights are unavoidable omissions, welcome to leave a message erratum, exchange.

The browser

The main function of the browser is to send a request to the server to display the target network resource in a window. With the popularity of the browser, Javascript was born as an accessory tool for the browser, mainly for simple verification of the browser side.

The main functions of the browser can be summarized as follows

  • Show the resource
  • Functional interaction

Browser kernel

The browser kernel is the core program that supports the browser to run. Corresponding to the main functions of the browser above, there are two main parts:

  • Rendering engine
  • Javascript engine

Rendering engine

Rendering engine: Converts HTML, CSS, Javascript text and corresponding resources into image results. Its main function is to parse resource files and render them on the screen. It is also what we normally call a narrow browser kernel

What immediately comes to mind when you think of a browser kernel?

  • Webkit (Safari, older versions of Chrome)
  • Trident (Old friend IE)
  • Gecko (Familiar Stranger Firefox)
  • Blink (new version of Chrome)

Their main job content is according to our HTML, CSS, JS definition, draw the corresponding page structure and presentation form.

Javascript engine

1. What is it? Why is that? What to do?

Javascript is an interpreted language. It does not compile before the code is run, converting the source code into intermediate code such as bytecode or machine code. Instead, it is compiled in real time during execution and executed as it is compiled.

So you need a functional module to do the compilation and transformation work, and that’s what the Javascript engine does. To summarize, the JS source code is parsed during runtime and converted into executable machine code and executed.

Interpreted: Code does not need to be compiled before it is run, but is compiled during execution, executing as it is compiled: Before code can be run, it needs to be converted into machine language or intermediate bytecode by the compiler and then executed. In general, interpreted languages are slow because compilation takes place during execution

2. Common Javascript engines

Common Javascript engines are as follows:

  • Chrome V8 engine (Chrome, Node, Opera)
  • SpiderMonkey (Firefox)
  • Nitro (Safari)
  • Available (Edge)

BUT, only relying on Javascript engine is not competent for browser JS processing work, in fact, Javascript engine is only a small building block in the browser JS related module.

The Runtime Runtime

In Web development, we usually don’t use Javascript engines directly. In fact, the Javascript engine works within an environment (container) that provides additional functionality (apis) that our code can use when executing.

Stack Overflow has a great answer about Javascript engines and Javascript Runtime.

In this view, unlike other compiled languages, compiled into machine code can be run directly on the host. JS is running in a host environment (a program that can recognize and execute JS code). This container must generally do two things:

  • Parse the JS code into executable machine language
  • Expose additional objects (apis) that can interact with JS code

The Javascript engine that does the first part of the work and the Javascript engine that does the second part of the work is actually what we call the Javascript Runtime in this section, so we can roughly think of Javascript Runtime as a scope created by the JS host environment, Within this scope JS can access a number of features provided by the host environment

What are the common JS hosting environments?

  • The web browser
  • node.js

In these two environments, the corresponding engine and runtime are as follows:

The host environment JS engine Runtime characteristics
The browser Chrome V8 engine DOM, Window objects, user events, Timers, etc
node.js Chrome V8 engine Require objects, buffers, Processes, fs, etc

The same JS engine, just in different environments, provides different capabilities.

In summary, the original JS was designed just to do web validation, but Javascript RuntimeJS can do more with different environments.

At this point, we have a brief understanding of the JS runtime.

Next, let’s take a look at how JS works by thinking about the logic of code execution in the browser environment.

JS engine

According to the above description, JS source code needs to be parsed and transformed by the JS engine before execution. It is generally considered that a JS engine consists of two main parts:

  • Memory heap reference type actual value, memory allocation place
  • Call stack Where basic types are stored, address names of reference types are stored, and code logic is executed

After the source code enters the JS engine, the code is read sequentially and allocated to the heap or stack according to different rules such as variable declaration and function execution.

Memory heap

Where the item reference type data is stored, the memory allocated by the system, the reference type data in JS, and the actual values are scattered here and there. In fact, the reference type storage is divided into two parts:

  • The real values are stored in memory, and the system allocates them where there is a proper place in memory according to its own situation. There is no strict order, so it is said to be scattered. Right
  • The physical memory address of the real value, which is stored on the stack as a base value

When a reference type is assigned to a new variable, it simply assigns the memory address stored on the stack to the new variable. This is equivalent to telling the new variable where the value is in memory, and fetching it when needed. It is not passing the actual value, because there is only one copy in memory. This is also what causes the citation problem

Execution stack

The execution stack is where the actual logical statements in the code are executed, as well as the values of the basic types generated during project execution.

The engine divides code into executable units, which are then placed on the execution stack and executed

So what are the executable units?

Executable unit, the standard term is the execution context JS, the execution context can be the following:

  • Global code —–> Global execution context
  • Function code —–> Function execution context
  • Eval code —–> Eval function execution context

What do these things have in common?

Global code can be thought of as an IIFE(execute now function), and functions in the common sense eval is a function that executes an incoming string

Functions. They’re all functions

So we can roughly understand: the execution stack, everything is a function call.

  • The first is that the entire JS code of the entry file is called first on the stack as an IIFE
  • Then, during actual execution, other functions are called, which are pushed onto the stack for execution

Therefore, the schematic of the JS engine can be updated as follows:

Single thread

JS is single threaded. Everyone knows that.

What do you mean? In a JS engine, code execution takes place in the call stack. The stack is a last in First Out (LIFO) data structure. Only functions at the top of the stack are processed, and when they’re done, they pop off the stack, and those at the top of the stack are executed…

An 🌰 :

The following JS file

function first() {
  second();  
}

function second() {  
  console.log('log fn'); } first(); .// Subsequent operations
Copy the code

The JS engine processes this code as follows (focusing only on function calls)

  1. The whole code goes to the top of the stack as an IIFE, pushed, main() function call and starts executing
  2. When first is called, the first function goes to the top of the stack and starts executing in the first body (while main is still executing).
  3. After entering the body of the first function, the second function is called and pushed to the top of the stack to execute in the body of the second function
  4. After entering the second body, the console.log function is called, and the console.log function is pushed to the top of the stack
  5. After the console.log function is printed, execution ends and the stack pops out
  6. Second is at the top of the stack, so execute the code after console.log in the second body, and find that there is no executable code. OK, declare second complete, and pop
  7. At this point, the first function enters the top of the stack, so execute the code in the body of the first function after the second function is called, and find that the code is empty, then the first function is completed, and pop up
  8. The main function at the top of the stack executes the following code

Therefore, JS single thread means that in the JS engine, the call stack for parsing and executing JS codes is unique. All JS codes are executed in this call stack according to the call order, and multiple functions cannot be executed at the same time.

What does a single thread mean?

This means that, under normal circumstances, the JS engine calls functions in sequence according to the logic written in the code, and only one function can be executed at any point in time. The outer function must wait until the inner function has returned a value.

Why single thread? JS was originally designed to be used in browsers. As a scripting language on browsers, it needs to interact with users’ operations and operate DOM. If it is multi-threaded, it needs to pay attention to the synchronization of states between threads.

Imagine that two functions can be executed simultaneously in a JS engine. If two functions operate on the same object, which one will prevail? Then, manipulating the DOM structure, thread A has deleted A node, and thread B is still working on that node, which is awkward.

And doing multithreaded state synchronization is not worth the cost, so go straight to single thread.

What’s the problem?

If a function takes a long time, the calling outer function must wait quietly for the function to complete before it can continue. If the function fails, the outer function cannot continue.

function foo(){
  bar()
  console.log('after bar')}function bar(){
  while(true) {}
}

foo()
Copy the code

For example, if foo calls bar, foo must wait for bar to complete before continuing to execute code. If bar is an infinite function, foo will not be able to execute code after the flowers fade.

This is an extreme case, of course, but there are several common cases in front end business scenarios that are problematic:

  • Timer delay operation
  • Network request
  • Web events, etc.

These are common asynchronous operations. The event is triggered and the result is not immediately available. Under the previous operating mode, the browser will block other operations and wait for the result to appear in the page.

Synchronous and asynchronous About synchronous and asynchronous operations, to borrow the words of The Great Deity Park Ling: general operations can be divided into two steps,

  1. A call
  2. results

Calls that get results immediately are made for synchronous calls that don’t get results immediately and require additional operations to get results for asynchronous calls

Thus the current model is problematic for asynchronous operations

The solution? The essence of the problem is the single-threaded working mode of THE JS engine, which only focuses on one thing. Those operations that have to be [executed to completion] and cause problems often can’t get results directly, but must go through additional operations to get results.

You can distribute these asynchronous operations to other modules, get the results, and put the callback function together on the main thread. This is the main idea behind Event loops.

Event loop is just one way to solve the asynchronous problem. Other ideas include:

  • polling
  • The event

Web API’s

As mentioned in the previous section, asynchronous operations can be handled by modules other than the JS engine, which in the browser is the Web API module

The Web API is essentially a collection of host environment features provided by the ABOVE JS Runtime.

In the browser, the following capabilities are included:

  • Event Listeners
  • HTTP request
  • Timing functions

Perfect cover for the above classes of cases that cause asynchronous problems

So far, we can get the following view:

In the JS engine, the execution stack encounters a synchronous function call, and after the result is directly executed, the stack pops out and the next function call continues

When an asynchronous function call is encountered, distribute the function toWeb APIThe asynchronous function then pops off the stack and continues to the next function call without blocking.

Question?

After these asynchronous operations are distributed to the Web API module, the main thread still needs to know the result and do the following operations. How can the Web API notify the main thread when it gets the result?

This requires the help of other modules.

The reference to the main thread here implies that there are other helper threads. Yes, JS is single-threaded, but that doesn’t mean the browser kernel is single-threaded. In fact, there are multiple threads within the Web API module, with each asynchronous operation processing module corresponding to a thread HTTP request thread, event processing thread, timer processing thread, and so on

Callback Queue

Callback queue, also known as event queue, message queue. This module is designed to help Web API modules handle asynchronous operations.

In a Web API module, after the result of an asynchronous operation is processed in the corresponding thread, the result is injected into the parameters of the asynchronous callback function and the callback function is pushed into the callback queue.

However, pushing only to the callback queue is not a problem because, as mentioned earlier, all JS execution takes place in the main thread call stack. After receiving the result, these asynchronous operations are pushed into the callback queue with the callback function and need to be executed on the main thread call stack at the appropriate time.

So, who knows when the right time will be?

Event Loop knows.

Callback queue A queue is a FIFO, first-in, first-out storage structure, which means that callbacks for asynchronous operations are executed in the order they were queued, not in the order they were called

Event Loop

The Event Loop constantly checks the main thread call stack and callback queue, and when it finds that the main thread is idle, it pushes the first task in the callback queue to the main thread. And so on and so on.

At this point, an asynchronous operation, going round and round and finally getting the result, succeeds without blocking any other operations.

overview

A complete illustration is as follows:

So far, this paper analyzes some simple mechanisms of the browser environment in the macro structure according to the steps of the browser to execute JS files. More execution details, such as lexical analysis and scope construction, will be covered in future articles.

About us

The front end team of Kuaigou Taxi focuses on sharing front-end technology and regularly pushes high-quality articles, which are welcome to be praised. This article will be published on our official account, just scan it!

Refer to the article

  • The Javascript Runtime Environment
  • What is the Execution Context & Stack in JavaScript?
  • The JavaScript runtime environment