1. Single-threaded JavaScript

JavaScript is a single-threaded language because of its purpose, as a scripting language for the browser, to interact with the user and manipulate the DOM.

If JavaScript is multithreaded, and there are two threads operating on a DOM node at the same time, one for deleting the DOM node and one for adding content to the DOM node, which thread should the browser use as the standard?

So, the purpose of JavaScript is that it has always been, and will always be, single-threaded.

HTML5 Web workers allow the main JavaScript thread to create multiple child threads, but these child threads are completely controlled by the main thread and cannot manipulate DOM nodes, so the nature of JavaScript single threads does not change.

2. Synchronous and asynchronous tasks

JavaScript is a single-threaded language, which means that tasks need to be queued until the first one is finished.

What if the previous task was very time-consuming? Such as operating IO devices, network requests, etc., the following tasks will be blocked, the page will be stuck, or even crash, and the user experience will be very poor.

If the main thread of JavaScript suspends these time-consuming tasks when they encounter them, executes later tasks first, and then returns to execute them when the pending task has a result, then the main thread is blocked by time-consuming tasks.

Thus, all tasks can be divided into two types, synchronous tasks and asynchronous tasks. Synchronous tasks are executed in the main thread, while asynchronous tasks are suspended, not executed in the main thread (let the main thread block and wait), and when they have results, they are put into the main thread for execution.

3. Task queue and Event Loop

3.1 Task Queue

A task queue is an event queue, also known as a message queue. When a pending asynchronous task is ready, an event is placed in the task queue, indicating that the task can be executed in the main thread.

The events in the task queue, in addition to the IO device events, also have network requests, mouse clicks, scrolling, etc. As long as the callback function is specified for the events, these events will enter the task queue when they happen, wait for the main thread to read, and then execute the corresponding callback function.

Callbacks are asynchronous tasks that are suspended, such as Ajax requests, that are executed after the request succeeds or fails.

A task queue is a first-in, first-out data structure in which the first events are read first as long as the main thread is empty.

3.2 the Event Loop

The main thread reads the Event from the task queue. This process is looped, so JavaScript is called an Event Loop.

4. Macro and micro tasks

Asynchronous tasks can be further divided into macro tasks and micro tasks, and there are two corresponding task queues, namely macro task queue and micro task queue.

4.1 the macro task

SetTimeout, setInterval, and setImmediate generate macro tasks

4.2 the task

RequestAnimationFrame, IO, read data, interaction events, UI Render, Promise.then, MutationObserve, process.nextTick generate microtasks

4.3 JavaScript Script Execution in the Browser

4.3.1 Process Description

A. JavaScript The script enters the main thread and starts to execute

B. If a macro task or a micro task is encountered during execution, suspend them respectively and put the event into the corresponding task queue only when the task is ready

C. After the script is executed, the stack is cleared

D. Read events successively from the microtask queue and put the corresponding callback functions into the execution stack. If macro tasks and microtasks are encountered during execution, the processing method is the same as THAT of B until the microtask queue is empty

E. The browser performs the rendering action, and the GUI rendering thread takes over until the rendering is complete

The F. JS thread takes over, reads the events in turn from the macro task queue, and puts the corresponding callback function on the execution stack to start the execution of the next macro task. The process is B -> C -> D -> E -> F, and so on

G. Execute the script until the execution stack, macro task queue, and micro task queue are empty

4.3.2 sample

4.3.2.1 sample a

/ / script

console.log(1)

setTimeout((a)= > {
  console.log(2)},0)

const p = new Promise((resolve) = > {
  setTimeout((a)= > {
    console.log(3)
    resolve()
  }, 1000)
  console.log(4)
})

p.then((a)= > {
  console.log(5)})console.log(6)

Copy the code

Implementation process

A. Put the script on the execution stack and execute it

B. Go to console.log(1) and enter 1

C. Execute setTimeout, encounter macro task, and suspend it. Due to the delay of 0ms, A timed event will be generated in macro task queue after 4ms, which is called timing A

D. The program continues down, executes new Promise(), runs its parameters, encounters a second scheduled task (macro task), calls it timed B, suspends it, executes console.log(4), prints 4

E. Suspend microtask p.teng () when it encounters it

F. If console.log(6) is encountered, 6 is displayed

G. The execution stack is cleared, the microtask queue is read and found to be empty, because p.teng () contains no ready, and its readiness depends on the execution of the first scheduled task (timing A)

H. Execute the browser rendering action with the stack empty and the microtask queue empty

I. Read the macro task queue. Read the first ready macro task (scheduled task A), put its callback function into the execution stack, execute console.log(2), enter 2

J. Perform stack emptie, microtask queue empty, render

K. Start the next ready macro task, timed task B, and put its callback function on the execution stack, execute console.log(3), print 3, and resolve(), p.chen () ready, and put the corresponding event on the microtask queue

O. Execute stack emptying, read the microtask queue, find that it is not empty, read the first ready event, and put its corresponding callback function into the execution stack execution, execute console.log(5), output 5

P. The execution stack is cleared, the microtask queue is empty, render, and the macro task queue is found to be empty, and the script execution is complete

The output is 1 4 6 2 3 5

4.3.2.2 example 2

async function async1 () {
  console.log('async1_1')
  await async2()
  console.log('async1_2')}async function async2 () {
  console.log('async2')}console.log('script start')
setTimeout((a)= > {
  console.log('setTimeout')},0)
async1()
new Promise(resolve= > {
  console.log('promise executor')
  resolve()
}).then((a)= > {
  console.log('promise then')})console.log('script end')
Copy the code

instructions

The async function returns a promise, such as async2, which returns an immediate resoved promise

Await will complete the synchronous code execution (async2) and then release the thread and suspend the asynchronous task (promise.then, immediate Resolved Promise), so an event will be added to the microtask queue before promise.then below

The output

If you understand the previous example, and then hunger and the explanation of this example information, the answer is clear:

script start => async1_1 => async2 => promise executor => script end => async1_2 => promise then => setTimeout

4.3.3 outside chain

Outside the chain

4.3.4 summary

If you consider JavaScript scripts as initial macro tasks, then JavaScript execution on the browser side looks like this:

Perform one macro task, then all the microtasks

Perform one more macro task, and then perform all the microtasks

.

This repeats, the execution stack and the task queue are empty

This section describes how to execute JavaScript scripts in 4.4 Node. js

JavaScript script execution is somewhat different in Node. js and browsers. The reason for the difference is that browsers have only one macro task queue, whereas Node. js has several macro task queues, and these macro task queues are executed in sequence, while microtasks are executed in between them

4.4.1 Execution Sequence

Each event type, The order from top to bottom ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ < -- --, perform setTimeout (), setInterval () callback │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ | |<--To perform firstprocess.nextTickAnd then to performMicroTask QueueThe callback │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │pending callbacks│ │<————— Execute from the previous oneTickdeferredI/OThe callback │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ | | <--To perform firstprocess.nextTickAnd then to performMicroTask QueueThe callback │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │idle.prepare│ < - - - - - internal call (negligible) │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ | | <--To perform firstprocess.nextTickAnd then to performMicroTask QueueThe callback | | ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │incoming:-Perform almost all callbacks, exceptclose callbacksIn order to | | | | | andtimersScheduling callback harmonizationsetImmediate(a) scheduling | |poll          |<-----|   connections, | callback, at the right time will be blocked at this stage) │ │ │ | │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │data.etc.│ │ | | | | | └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ | | <--To perform firstprocess.nextTickAnd then to performMicroTask QueueThe callback | ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │check│ < -- -- -setImmediate() callback will be at this stage to perform │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ | | <--To perform firstprocess.nextTickAnd then to performMicroTask QueueThe callback │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ┤close callbacks│ < -- -- -socket.on('close', .) └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘Copy the code

4.4.2 sample

4.4.2.1 Basic Examples

console.log(1)

setTimeout((a)= > {
  console.log('timer1')
  Promise.resolve().then((a)= > {
    console.log('promise1')})},0)

setTimeout((a)= > {
  console.log('timer2')
  Promise.resolve().then((a)= > {
    console.log('promise2')})},0)

console.log(2)

Copy the code

This code is executed in the browser as follows: 1 2 Timer1 promise1 Timer2 promise2

In Node. js, the result is 1 2 timer1 Timer2 promise1 promise2

4.4.2.2 Sequence of setTimeout and setImmediate

The sequence of the two is obvious from the figure above. The Timers queue runs on the check queue, but the event is already in place

setTimeout((a)= > {
  console.log('timeout')},0)

setImmediate((a)= > {
  console.log('immediate')})Copy the code

Node. js runs the following code: immediate timeout

When the program is running, the timer event is not ready, so the queue is empty when it reads the timer queue for the first time. Continue to execute downward, and the ready event is read in the check queue. Therefore, execute immediate and then timeout, because even if the delay time of setTimeout is not 0, However, node.js is usually set to 1ms. Therefore, if the time for node to prepare the Event Loop is longer than 1ms, it will output timeout first and then immediate; otherwise, it will output timeout first and then timeout

const fs = require('fs')

// Read the file
fs.readFile('xx.txt', () => {
  setTimeout((a)= > {
    console.log('timeout')
  })

  setImmediate((a)= > {
    console.log('immediate')})})Copy the code

The preceding code must be output in the following order: immediate timeout, for the following reasons:

SetTimeout and setImmediate are both written in the I/O callback, meaning that the poll phase is followed by the Check phase, so that setImmediate takes precedence no matter how fast setTimeout is ready (1ms). Essentially, Start with the poll phase rather than a Tick initialization phase.