JS event loop



When I first learned the front end, I always heard that JS is single threaded, single threaded and single threaded. In fact, the complete JS execution engine should be single threaded in the browser environment.

So what is a thread? Why is JS single threaded?

1. Process and thread

The main difference between processes and threads is that they are different ways of operating system resource management. A process has an independent address space. When a process crashes, it will not affect other processes in protected mode. Threads are just different execution paths in a process.

My understanding, a program running, at least one process, a process has at least one thread, process is the smallest unit of memory resources allocated by the operating system, thread is the smallest unit of CPU scheduling.

For example, a process is like a factory. A thread is the worker in the factory. There are multiple workers in the factory, and the workers can share the resources in the factory. Multiple workers can coordinate their work together, similar to multithreading concurrent execution.

2. Browser is multi process

Open the Windows Task Manager, you can see that the browser has many processes, each tab page is a separate process, so a page crash will not affect other pages

JS event loop

The browser includes the following processes:

  • Browser process: the main process of the browser (responsible for coordination and control), only one
  • Third party plug-in process: each type of plug-in corresponds to a process, which is created only when the plug-in is used
  • GPU process: at most one, used for 3D rendering, etc
  • Browser rendering process (browser kernel) (render process, internal is multithreaded): by default, each tab page has one process, which does not affect each other

3. Browser rendering process

The browser rendering process is multithreaded, which is also the focus of front-end people. It includes the following threads:

  • GUI rendering thread

    • Responsible for rendering browser interface, parsing HTML, CSS, building DOM tree and renderobject tree, layout and drawing.
    • When the interface needs to be repaint or reflow due to some operation, the thread will execute
    • GUI rendering thread and JS engine thread are mutually exclusive. When JS engine is executing, GUI thread will be suspended (equivalent to being frozen), GUI update will be saved in a queue and executed immediately when JS engine is idle.
  • JS engine thread

    • Also known as the JS kernel, it handles JavaScript scripts( (e.g. V8)
    • JS engine thread is responsible for parsing JavaScript script and running code.
    • JS engine has been waiting for the arrival of tasks in the task queue, and then processes them. There is only one JS thread running JS program in a tab page (renderer process) at any time
    • Also note that Gui rendering thread and JS engine thread are mutually exclusive, so if JS execution time is too long, it will cause page rendering incoherence, resulting in page rendering load blocking.
  • Event triggered thread

    • It belongs to the browser rather than the JS engine, which is used to control the event loop (it is understandable that the JS engine itself is too busy, and needs the help of another thread of the browser)
    • When JS engine executes code block such as setTimeout (it can also come from other threads of browser kernel, such as mouse click, AJAX asynchronous request, etc.), it will add the corresponding task to the event thread
    • When the corresponding event meets the trigger conditions and is triggered, the thread will add the event to the end of the queue to wait for JS engine to process
    • Note that due to the single thread relationship of JS, the events in the waiting queue have to be queued for processing by JS engine (it will be executed only when JS engine is idle)
  • Timed trigger thread

    • The thread of setinterval and setTimeout in legend
    • Browser timer is not counted by JavaScript engine. (because JavaScript engine is single threaded, if it is in blocked thread state, it will affect the accuracy of timing.)
    • Therefore, the timer is timed and triggered by a separate thread (after timing, it is added to the event queue and executed after JS engine is idle)
    • Note that W3C stipulates in HTML standard that the time interval less than 4ms in setTimeout is counted as 4ms.
  • Asynchronous HTTP request thread

    • After the XMLHttpRequest is connected, a new thread request is opened through the browser
    • When a state change is detected, if a callback function is set, the asynchronous thread will generate a state change event and put the callback into the event queue. It is then executed by the JavaScript engine.

4. JS engine is single threaded

One reason why JS engine is single threaded is that the complexity of multithreading is higher. Another problem is that the result may not be expected: suppose JS engine is multi-threaded, and there is a div, thread a gets the attribute of the node, thread B deletes the node, so what? How to operate under multithread concurrent execution?

Maybe this is why JS engine is single threaded, and the expected execution of code from top to bottom reduces the programming cost, but there are also other problems. If an operation is time-consuming, for example, a calculation operation for loop traverses 100 million times, it will block the following code and cause page jam

Mutual exclusion between GUI rendering thread and JS engine thread, is to prevent unexpected results from rendering, because JS can obtain dom. If you modify these element attributes and render the interface at the same time (that is, JS thread and UI thread run at the same time), the element data obtained before and after the rendering thread may be inconsistent. So when JS thread is executing, rendering thread will be suspended; When the rendering thread executes, the JS thread will hang,So JS will block page loading, which is why JS code should be placed after the body tag and before all HTML content; In order to prevent blocking the page configuration and causing white screen

5. WebWorker

As mentioned above, JS is single threaded, that is to say, all tasks can only be completed on one thread, and only one thing can be done at a time. The task ahead is not finished, the task behind can only wait. With the enhancement of computer computing power, especially the emergence of multi-core CPU, single thread brings great inconvenience, unable to give full play to computer computing power.

Web worker is to create a multithreading environment for JavaScript, allowing the main thread to create a worker thread and assign some tasks to the latter to run. While the main thread is running, the worker thread is running in the background, and they do not interfere with each other. Wait until the worker thread completes the calculation task, and then return the result to the main thread. The advantage of this is that if some computation intensive or high latency tasks are burdened by the worker thread, the main thread (usually responsible for UI interaction) will be very smooth and will not be blocked or slowed down.

Web worker has several characteristics

  • Homologous restriction: the script file assigned to the worker thread must have the same source as the script file of the main thread.
  • DOM restrictions: DOM cannot be manipulated
  • Communication: the worker thread and the main thread are not in the same context. They cannot communicate directly and must be completed by message.
  • Script restrictions: cannot execute alert() and confirm() methods
  • Document restrictions: unable to read local file

6. Browser rendering process

The following is a simple process for the browser to render the page. In detail, we can open another article ~. ~: “what happened from the input URL to the completion of page rendering”

    1. The user enters the URL and DNS resolves it to the request IP address
    1. The browser establishes a connection with the server (TCP protocol, three times handshake), and the server processes and returns the HTML code block
    1. The browser receives the processing, parses HTML into DOM tree, parses CSS into cssobj
    1. DOM tree and cssobj are combined to form render tree
    1. JS calculates, arranges and redraws according to the render tree
    1. GPU synthesis, output to screen

JS event loop

There’s a lot of wrangling on the top, and now we start to get to the point

1. Synchronous task and asynchronous task

JS has two tasks:

  • Synchronization task
  • Asynchronous task

Synchronous task, as the name suggests, means that the code is executed synchronously, while asynchronous code means that the code is executed asynchronously. Why is JS divided in this way?

Let’s assume that all JS code is executed synchronously. A packed JS has 10000 lines of code. If you encounter setTimeout at the beginning, you need to wait 100 seconds to execute the following code… If there are some IO operations and asynchronous requests in the middle, it’s all crashing

// todo

//Omit 10000 lines of code below

Because synchronous execution of asynchronous tasks is time-consuming, and most of the code is synchronous code, we can execute synchronous code first, and give these asynchronous tasks to other threads to execute, such as timed trigger thread, asynchronous HTTP request thread, etc., and then wait for these asynchronous tasks to complete before executing them. suchStrategy of scheduling synchronous and asynchronous tasksThat’s itJS event loop

    1. Execute the whole code. If it is a synchronous task, it will be executed directly on the main thread to form an execution stack
    1. When an asynchronous task is encountered, such as a network request, it will be executed by other threads. When the asynchronous task is finished, a callback function will be inserted into the event queue
    1. Once all the synchronous tasks in the execution stack are finished (that is, the execution stack is empty), the event queue will be read, and a task will be taken to the execution stack to start execution
    1. Repeat step 3 all the time

JS event loop

This is the event loop, which ensures that the synchronous and asynchronous tasks are executed in an orderly manner. Only when all the synchronous tasks are finished, the main thread will read the event queue to see if there are tasks (the second callback after the asynchronous task is finished) to be executed, one at a time.

JS event loop

The setTimeout of the old growth talk

setTimeout(() => {
  Console.log ('asynchronous task ');
}, 0);

Console.log ('synchronization task ');

I believe you can easily understand the following execution results. The main thread scans the whole code:

  • If an asynchronous task setTimeout is found, it will be suspended and sent to the timer trigger thread (the timer will put the result into the event queue in the form of callback after waiting for the specified time, waiting for the main thread to read and execute),
  • Find the synchronization task console and plug it into the execution stack
  • It’s done from top to bottom
  • When the execution stack is idle, check whether there are tasks in the event queue (at this time, the timer is finished), and take out a task to be executed in the execution war
  • Event queue clear

JS event loop

2. Macro task and micro task

1. Macro task and micro task

In addition to generalized synchronous task and asynchronous task, tasks in JavaScript single thread can be divided into macro task and micro task

  • Macro task: script (whole code), setTimeout, setinterval, setimmediate, I / O, UI rendering
  • process. nextTick, Promises, Object. observe, MutationObserver

2. Event loop, macro task and micro task

The code executed by the execution stack is a macro task (including getting an event callback from the event queue and putting it into the execution stack to execute each time)

Then check whether the micro task is found in this cycle. If it exists, read and execute all the micro tasks from the task queue of the micro task in turn, then read and execute the tasks in the task queue of the macro task, and then execute all the micro tasks. The execution order of JS is the macro task micro task in each event loop.

  • The first event loop, the whole code as a macro task into the main thread
  • Synchronous code is directly pushed to the execution stack for execution, and when encountering asynchronous code, it will be suspended and executed by other threads (after execution, it will call back to the event queue)
  • After the synchronization code is executed, the micro task queue is read. If all micro tasks are executed, the micro task is cleared
  • Page rendering
  • Take a macro task from the event queue and put it into the execution stack
  • So many times

JS event loop

Translate it with code

#Macro task
for (let macrotask of macrotask_list) {
  #Execute a macro task
  #Perform all micro tasks
  for (let microtask of microtask_list) {
  #UI rendering

JS event loop

3. Event loop and page rendering

In ECMAScript, micro task is called jobs, and macro task can be called task.

In order to enable JS internal tasks and DOM tasks to be executed orderly, the browser will re render the page after the execution of one task and before the execution of the next task

(task > rendering > Task – >…)

Let’s take a look at an example. We have a div with an ID of app

< div id = "app" > macro task, micro task < / div >

What happens when you execute the following code?

document. querySelector('#app').style.color = 'yellow'; 

Promise. resolve(). then(() => {
  document. querySelector('#app').style.color = 'red'; 

setTimeout(() => {
  document.querySelector('#app').style.color = 'blue'; 
  Promise.resolve(). then(() => {
    for (let i = 0; i < 99999; i++) {
}, 17); 

Let’s look at the running results directly

JS event loop

The text will turn red first, and then turn blue after a period of time; Let’s analyze how the program works

  • In the first round of event cycle, when the first synchronization task is encountered, it is pushed into the execution stack for execution, and the DOM operation makes the textYellowingWhen you encounter the second one, the promise micro task is pushed into the micro task queue, and you continue to go down. When you encounter the macro task setTimeout, the timer triggers the thread
  • After the first round of macro task is finished, check the micro task queue, find out there is a task, execute and clear the queue, DOM operation makes the textTurn redAt this time, the setTimeout is not finished
  • GUI rendering thread,Redden text
  • In the second cycle, the execution stack is empty, check the micro task queue is empty, continue to detect the event queue, find that there is a result, and insert it into the execution stack for execution
  • Execute the callback in setTimeout to execute the first synchronization task. DOM operation makes the text blue. The second one is that the micro task is put into the micro task queue. When the synchronization task is finished, it is found that there is task execution in the micro task and the queue is cleared. The console in the micro task is the synchronization task. At this time, the JS thread is always executing and the GUI rendering thread is suspended, Wait until the synchronization task is finished
  • GUI rendering thread,Turn text Blue
  • End of event cycle

HTML5 standard stipulates that the minimum value (minimum interval) of the second parameter of settimeout() should not be less than 4 ms. if it is lower than this value, it will be automatically increased.

One of the problems is that Google does not follow the UI rendering between two macro tasks (Google’s optimization strategy?), Set the setTimeout event to 0, and find that the text will not change from black > Red > blue, but directly black > blue. For the simulation effect, I set the time interval to 17ms (my screen is 60Hz, that is, 16. 67ms to refresh once)

JS event loop

4. Vue. $nextTick

Friends who use Vue may often use this API in their work

Delay the callback until after the next DOM update cycle. Use the data immediately after you modify it, and then wait for the DOM to update.

Its internal implementation is to use microtask to delay the execution of a piece of code (to obtain the value of DOM node), that is, to execute microtask after all the current synchronous code is executed

Vue nexttick source code

reference resources

The difference between original process and thread

Web worker tutorial

JS is a single thread. Do you know how it works?

All the pictures in this article are from the Internet

Source code

Source code