Reference links:

Juejin. Cn/post / 684490…

Juejin. Cn/post / 684490…

1. Single-threaded JS

As a scripting language mainly running in browsers, ONE of the main uses of JS is to manipulate DOM.

If js has two threads operating on the same DOM at the same time, which thread should the browser listen to? How to determine the priority?

To avoid this problem, JS must be a single-threaded language, and that will not change in the future.

2. Execution stack and task queue

Because JS is a single-threaded language, when it comes to asynchronous tasks (such as Ajax operations), it is not possible to wait for the asynchronous task to complete and then continue to execute. During this time, the browser is idle, which obviously leads to a huge waste of resources.

(1) Execution stack

When a function is executed, the user clicks the mouse once, Ajax completes, an image is loaded, etc., as long as the callback function is specified, these events will enter the task queue and wait for the main thread to read, following the first-in, first-out principle.

To execute a task in the task queue, the executed task is called the execution stack.

(2) Main thread

To be clear, the main thread is a different concept from the execution stack. The main thread specifies which event in the execution stack is now executed.

Main thread loop: that is, the main thread constantly reads events from the stack and executes all synchronized code in the stack.

When an asynchronous event is encountered, instead of waiting for the asynchronous event to return a result, the event is suspended in a separate Queue from the execution stack, called a Task Queue.

When the main thread has finished executing all the code in the stack, it will check to see if there are any tasks in the task queue. If so, the main thread executes those callbacks in the task queue in turn.

(3) Synchronous and asynchronous tasks

  1. Synchronous and asynchronous tasks go to different execution “places”, synchronous tasks go to the main thread, asynchronous tasks go to the Event Table and register their callback functions
  2. When the specified Event completes, the Event Table moves the callback function to the Event Queue.
  3. If the tasks in the main thread are empty after execution, the Event Queue will read the corresponding callback function and enter the main thread for execution.
  4. This process is repeated over and over again, known as an Event Loop.

Pure synchronization task:

console.log('start')

console.log('end')
Copy the code

If you print “start” and then “end”, the code will enter the synchronization queue and be executed sequentially.

Synchronous + asynchronous:

console.log('start')

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

console.log('end')
Copy the code

Note:

Note: setTimeout’s delay indicates XXX time after the callback is added to the asynchronous queue, not XXX time after the callback is executed, so its timing is not precise. Suppose setTimeout specifies execution in 2 seconds, but there is a function in the synchronization queue that takes a long time to execute, or even 1 second. In this case, the setTimeout callback will wait at least 1 second for the synchronization task to complete before executing. The setTimeout callback will execute for more than 2 seconds, or at least 3 seconds.

When the function call stack reaches setTimeout, setTimeout will put the callback function into the asynchronous queue at a specified point in time, and execute the setTimeout callback immediately after the synchronization queue is completed. Therefore, the result is as follows: Start, end, setTimeout.

3. Macro versus micro tasks

Both micro tasks and macro tasks are asynchronous tasks, which belong to the same queue. The main differences lie in their execution sequence and the direction and value of Event Loop. So what’s the difference between them?

Macro tasks include:

  • Script (whole code)
  • SetTimeout, setInterval, setImmediate (belonging to Node),
  • I/O
  • UI rendering

Ajax requests do not belong to macro tasks. When the JS thread meets an Ajax request, it will hand the request to the corresponding HTTP thread for processing. Once the request returns the result, it will put the corresponding callback into the macro task queue and wait for the request to complete execution.

Jobs include:

  • Process. nextTick (belonging to Node)
  • Promise
  • Object. Observe (deprecated)
  • MutationObserver(new html5 feature)

Note that most of the macro/micro tasks here refer to callbacks for operations of the corresponding type, whereas the Promise microtask corresponds not to its immediate callback, but to the promise.then callback

Example 1:

console.log('start')

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

new Promise(function(resolve) {
    console.log('promise')
    resolve()
}).then(function() {
    console.log('promise resolved')})console.log('end')
Copy the code

Execution process parsing:

  1. Set up the execution context, enter the execution stack to start executing the code, and print start
  2. As you go down, setTimeout is encountered, and the callback function is placed in the macro task queue, waiting to execute
  3. Moving on, there is a new Promise whose callback will not be placed on any other task queue, so it executes synchronously, printing the Promise, but when resolve is resolved,.then puts its internal callback into the microtask queue
  4. When you get to the bottom of the code, print end. At this point, the main execution stack is emptied and the microtask queue is searched for executable code
  5. It finds the code that was put in the microtask queue, prints out the Promise Resolved, and the first loop ends
  6. The second loop starts from the macro task and checks if there is executable code in the macro task queue. If there is one, print timeout

So, the print order is start–> PROMISE –>end–> Promise resolved–>timeout

Example 2:

console.log('First loop main stack starts')

setTimeout(function() {
    console.log('Second loop starts, first macro task in macro task queue executing')
    new Promise(function(resolve) {
        console.log('The first macro task in the macro task queue continues to execute')
        resolve()
    }).then(function() {
        console.log('Microtask execution for second loop microtask queue')})},0)

new Promise(function(resolve) {
    console.log('First loop main stack in progress... ')
    resolve()
}).then(function() {
    console.log('First loop microtask, end of first loop')
    setTimeout(function() {
        console.log('Second macro task execution of second loop macro task queue')})})console.log('First loop main stack completes')
Copy the code
  • First cycle

1: Enter the execution stack execution code, print the beginning of the first cycle of the main execution stack

2: When setTimeout is encountered, the callback is placed in the macro task queue for execution

3: The promise states that the process is synchronous, printing the first time the main stack is in progress… Then, the callback is put into the microtask queue

4: Print the completion of the first loop main execution stack

5: Check whether the microtask queue has executable code, there is a task put in the third step, print the first loop microtask, the first loop ends, the first loop ends, at the same time encounter setTimeout, put the callback into the macro task queue

  • Second cycle

1: from the macro task, check the macro task queue, found that there are two macro tasks, respectively the second step of the first cycle and the fifth step of the first cycle are put into the task, first execute the first macro task, print the second cycle, the first macro task in the macro task queue execution

If a promise statement is encountered and the first macro task in the macro task queue is resolved, the. Then callback will be put into the micro task queue, which is the first macro task in the macro task queue

3: the synchronization code in the first macro task is completed, check the microtask queue, and find a piece of code put in the second step, execute the microtask of printing the second loop of the microtask queue, then the first macro task is completed

4: the second macro task is executed. The second macro task that prints the macro task queue of the second loop is executed. All the task queues are cleared and the execution is complete

Example 3:

The code below “await” and “await” can be seen as:

New Promise (the console log (' async2)), then (() = > {the console. The log (' async1 end ')})Copy the code

Core idea: the macro task of the first event cycle is preferentially executed, and the micro task encountered during execution is thrown to the micro task queue. After the execution of the first macro task, all the micro tasks encountered during the first macro task are executed. Then follow this rule to execute the macro task of the next loop… (If a macro task is encountered during microtask execution, it is also thrown into the macro task pair column.)