(Reproduced) Execution process of js engine (2)

Overview

The execution process of the js engine is mainly divided into three stages, namely syntax analysis, pre-compilation and execution. In the last article, we introduced the syntax analysis and pre-compilation phases, so let's make a brief summary first, as follows:

  • Syntax analysis : Check the syntax of the loaded code blocks respectively. If the syntax is correct, it will enter the pre-compilation stage; if it is incorrect, stop the execution of the code block, find the next code block and load it, and enter the syntax of the code block again after the loading is complete. Analysis phase

  • Pre-compilation : After passing the syntax analysis phase and entering the pre-compilation phase, a variable object is created (the arguments object is created (in the function running environment), the function declaration is parsed in advance, and the variable declaration is promoted), and the scope chain and this point are determined.

If you still have questions, look back at the execution process of the js engine in the previous article (1) .


This article mainly analyzes the third stage of js engine execution – the execution stage . Before the analysis, we first consider the following two questions:

js is single-threaded. In order to avoid code parsing blocking, asynchronous execution is used, so what is its asynchronous execution mechanism?

Through the event loop (Event Loop), understand the principle of the event loop and understand the asynchronous execution mechanism of js. This article mainly introduces.

js is single-threaded, so does it mean that there is only one thread participating in the execution of js?

No, there will be four threads involved in the process, but only the JS engine thread will always execute the JS script program, and the other three threads will only assist and not participate in code parsing and execution. The threads participating in the js execution process are:

  • JS engine thread : Also known as the JS kernel, the main thread responsible for parsing and executing Javascript scripts (such as the V8 engine)

  • Event trigger thread : belongs to the browser kernel process and is not controlled by the JS engine thread. Mainly used to control events (such as mouse, keyboard and other events), when the event is triggered, the event trigger thread will push the event handler into the event queue , waiting for the JS engine thread to execute

  • Timer trigger thread : It mainly controls the timer setInterval and the delayer setTimeout, which are used for the timing of the timer. When the timing is completed and the trigger condition of the timer is satisfied, the processing function of the timer is pushed into the event queue and waits for the execution of the JS engine thread. .
    Note: W3C stipulates in the HTML standard that the time interval when setTimeout is lower than 4ms is counted as 4ms.

  • HTTP asynchronous request thread : After connecting through XMLHttpRequest, through a new thread opened by the browser, when monitoring the state change of readyState, if the callback function of this state is set, the processing function of this state will be pushed into the event queue and wait for the JS engine thread implement.
    Note: The number of concurrent connections requested by a browser to a domain name is limited. The limit is 6 for Chrome and Firefox, and 10 for ie8.

Summary: Only the JS engine thread is always executing the JS script program, and the other three threads are only responsible for pushing the processing functions that meet the trigger conditions into the event queue, waiting for the JS engine thread to execute.

 

execution phase

Let's analyze a typical example (from Tasks, microtasks, queues and schedules , it is recommended to read with a good English foundation, very good article):

console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});

console.log('script end');

 

Here I directly divide the code structure of the example, briefly describe the analysis and execution process, and do not explain the concepts and principles in this process for the time being. The concepts and principles will be explained in detail below:

  1. Macro-tasks, which are divided into synchronous tasks and asynchronous tasks according to the execution order

    • Sync task

      console.log('script start');

      console.log('script end');
    • asynchronous task

      setTimeout(function() {
      console.log('setTimeout');
      }, 0);
  2. micro-task


    Promise.resolve().then(function() {
    console.log('promise1');
    }).then(function() {
    console.log('promise2');
    });

During the execution of the JS engine, after entering the execution stage, the execution sequence of the code is as follows:

Macro task (synchronous task) --> Micro task --> Macro task (asynchronous task)

 

The output is:

script start
script end
promise1
promise2
setTimeout

 

In the ES6 or Node environment, JS tasks are divided into two types, namely macro-tasks and micro-tasks . In the latest ECMAScript, micro-tasks are called jobs, and macro-tasks are called tasks. , and their execution order is as above. Many people may not understand the above analysis, so we will continue to analyze the above example in detail.

 

macro task

Macro-tasks can be divided into synchronous tasks and asynchronous tasks :

  • Synchronous tasks refer to tasks that are executed in sequence on the main thread of the JS engine. Only after the execution of the previous task is completed, the next task can be executed to form an execution stack (function call stack).

  • Asynchronous task means that it does not directly enter the main thread of the JS engine, but when the trigger conditions are met, the relevant thread pushes the asynchronous task into the task queue , waits for the completion of the task execution on the main thread of the JS engine, and reads it when idle. Executed tasks such as asynchronous Ajax, DOM events, setTimeout, etc.

Understanding the execution order of synchronous tasks and asynchronous tasks in macro tasks is equivalent to understanding the asynchronous execution mechanism of JS - Event Loop .

 

event loop

The event loop can be understood as consisting of three parts, namely:

  • Main thread execution stack

  • Asynchronous task waiting to be triggered

  • task queue

The task queue (task queue) is to manage the event tasks with the data structure of the queue, which is characterized by first-in, first-out, last-in, last-out .


Here is a direct quote from a famous picture (refer to Philip Roberts' speech "Help, I'm stuck in an event-loop") to help us understand, as follows:
Event Loop

During the execution of the main thread of the JS engine:

  • First execute the synchronization task of the macro task, and form an execution stack on the main thread, which can be understood as a function call stack;

  • When a function in the execution stack calls some asynchronously executed APIs (such as asynchronous Ajax, DOM events, setTimeout and other APIs), the corresponding threads (Http asynchronous request thread, event trigger thread and timer trigger thread) will be opened for monitoring and control

  • When the event of the asynchronous task meets the triggering conditions, the corresponding thread will push the event handler into the task queue , waiting for the main thread to read and execute it.

  • When the tasks on the main thread of the JS engine are executed, the events in the task queue will be read, and the event tasks in the task queue will be pushed into the main thread and executed in the order of the task queue.

  • When the task on the main thread of the JS engine is executed, the event task in the task queue will be read again, and so on, this is the process of the event loop (Event Loop)

If you still can't understand, then let's take the above example for a detailed analysis again. The code part of the macro task in this example is:

console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

console.log('script end');

 

The code execution process is as follows:

  1. The main thread of the JS engine is executed in code order. When it is executed console.log('script start');, the main thread of the JS engine thinks that the task is a synchronous task , so it immediately executes the output script start, and then continues to execute downward;

  2. When the main thread of the JS engine executes setTimeout(function() { console.log('setTimeout'); }, 0);, the main thread of the JS engine thinks that setTimeout is an asynchronous task API, and then applies to the browser kernel process to start the timer thread to time and control the setTimeout task. Since W3C stipulates in the HTML standard that the time interval when setTimeout is lower than 4ms is counted as 4ms, then when the timer reaches 4ms, the timer thread pushes the callback processing function into the task queue to wait for the main thread to execute, and then the main thread of the JS engine continues to execute under

  3. When the main thread of the JS engine is executed console.log('script end');, the main thread of the JS engine thinks that the task is a synchronous task , so it immediately executes the output script end

  4. After the tasks on the main thread of the JS engine are executed (script start and script end are output), and the main thread is idle, it starts to read the event tasks in the task queue, pushes the event tasks in the task team to the main thread, and presses the task queue. Execute sequentially, and finally output setTimeout, so the output sequence isscript start script end setTimeout

The above is the whole process of the JS engine executing the macro task.

After understanding the process, let's do some extended thinking:

We all know that setTimeout and setInterval are timers for asynchronous tasks, and they need to be added to the task queue to wait for the main thread to execute. So using setTimeout to simulate the implementation of setInterval, is there any difference?

The answer is different, let's think about it:

  • setTimeout implements setInterval only through recursive calls

  • setTimeout pushes the event to the task queue when the specified time arrives. Only after the setTimeout event in the task queue is executed by the main thread will it continue to push the event to the task queue again when the specified time is reached, then The event execution of setTimeout must be longer than the specified time, and the specific difference is related to the code execution time

  • setInterval pushes an event into the task queue at precise intervals every time, regardless of whether the previous setInterval event has been executed or not, so there may be an accumulation of event tasks for setInterval, causing the code of setInterval to be repeatedly executed multiple times, affecting Page performance.

Based on the above analysis, using setTimeout to achieve timing function is better than setInterval. Of course, if you do not need to be compatible with lower versions of IE browsers, using requestAnimationFrame is a better choice.

We continue to do further thinking, as follows:

The high frequency of triggering events (such as scrolling events) will affect page performance and even cause page freezes. Can we use the principle of timers to optimize?

Yes, we can use setTimeout to realize the principle of timer and optimize the events triggered by high frequency. The realization point is to combine multiple trigger events into one, which is anti- shake and throttling . This article will not explain it in detail first. You can do your own research, and I will open another article to analyze it when I have the opportunity.

 

microtasks

Microtask is a task type that appears in es6 and node environments. If we do not consider es6 and node environments, we only need to understand the execution process of the macro task event loop, but in es6 and node environments, we need to Understand the execution order of microtasks.
The main APIs of micro-task are: Promise, process.nextTick

Here we directly quote a flow chart to help us understand, as follows:
task

There are two kinds of tasks executed in macro tasks, namely synchronous tasks and asynchronous tasks , because asynchronous tasks will advance the task queue (task queue) only when the trigger conditions are met, and then wait for the tasks on the main thread to finish executing, and then read Take the task events in the task queue, and finally push the main thread to execute, so here the asynchronous task, that is, the task queue, is regarded as a new macro task. The execution process is shown in the figure above:

  1. Execute the synchronization task in the macro task , and the execution ends;

  2. Check whether there are executable microtasks , execute all microtasks if there are any, then read the task events of the task queue, and push the main thread to form a new macro task; if not, read the task events of the task queue and push the main thread to form new macro task

  3. Execute the event task of the new macro task , and then check whether there is an executable micro task, so that the cycle is repeated

This is the detailed event loop after adding microtasks. If you haven't understood it yet, let's make a comprehensive analysis of the first example, as follows:

console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});

console.log('script end');

 

The execution process is as follows:

  1. 代码块通过语法分析和预编译后,进入执行阶段,当JS引擎主线程执行到console.log('script start');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script start,然后继续向下执行;

  2. JS引擎主线程执行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主线程认为setTimeout是异步任务API,则向浏览器内核进程申请开启定时器线程进行计时和控制该setTimeout任务。由于W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms,那么当计时到4ms时,定时器线程就把该回调处理函数推进任务队列中等待主线程执行,然后JS引擎主线程继续向下执行

  3. JS引擎主线程执行到Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });,JS引擎主线程认为Promise是一个微任务,这把该任务划分为微任务,等待执行

  4. JS引擎主线程执行到console.log('script end');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script end

  5. 主线程上的宏任务执行完毕,则开始检测是否存在可执行的微任务,检测到一个Promise微任务,那么立刻执行,输出promise1promise2

  6. 微任务执行完毕,主线程开始读取任务队列中的事件任务setTimeout,推入主线程形成新宏任务,然后在主线程中执行,输出setTimeout

最后的输出结果即为:

script start
script end
promise1
promise2
setTimeout

 

 

总结

The above is the whole process of JS engine execution. The execution process of JS engine is actually not complicated. It can be understood as long as you think and study more. After understanding the process, you can improve your understanding of JS to a certain extent.

 [Original link](https://heyingye.github.io/2018/03/26/js%E5%BC%95%E6%93%8E%E7%9A%84%E6%89%A7%E8%A1 %8C%E8%BF%87%E7%A8%8B%EF%BC%88%E4%BA%8C%EF%BC%89/)

references

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324704323&siteId=291194637