JavaScript execution mechanism (macrotasks and microtasks)

The purpose of this article is to ensure that you thoroughly understand the execution mechanism of javascript. If you still don’t understand after reading this article, you can beat me.

Whether you are a javascript novice or a veteran, whether you are interviewing for a job or doing daily development work, we often encounter such a situation: Given a few lines of code, we need to know its output content and order. Because javascript is a single-threaded language, we can conclude that:

  • JavaScript is executed in the order in which statements appear

Seeing this, readers are going to hit someone: Don't I know that js is executed line by line? What do you need to say? Don't be impatient, because js is executed line by line, so we think that js is like this:

let a = '1';
console.log(a);

let b = '2';
console.log(b);

But in fact js is like this:

setTimeout(function(){
    console.log('定时器开始啦')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});

console.log('代码执行结束');

According to the concept that js is executed in the order in which the statements appear , I confidently write down the output results:

//"定时器开始啦"
//"马上执行for循环啦"
//"执行then函数啦"
//"代码执行结束"

Going to chrome to verify, the result is completely wrong, and I am instantly confused. Is it executed line by line?

We really have to thoroughly understand the execution mechanism of javascript.

1. About javascript

JavaScript is a single-threaded language. Web-Worker was proposed in the latest HTML5, but the core of JavaScript is single-threaded has not changed. So all javascript versions of "multithreading" are simulated with a single thread, and all javascript multithreading is a paper tiger!

2. javascript event loop

Since js is single-threaded, it is like a bank with only one window. Customers need to queue up to handle business one by one. Similarly, js tasks must be executed one by one. If one task takes too long, then the latter task must also wait. So here comes the question, if we want to browse the news, but the ultra-high-definition pictures contained in the news load very slowly, should our webpage be stuck until the pictures are fully displayed? So smart programmers divide tasks into two categories:

  • sync task
  • asynchronous task

When we open the website, the rendering process of the web page is a lot of synchronous tasks, such as the rendering of the page skeleton and page elements. And tasks that take up a lot of resources and take a long time, such as loading pictures and music, are asynchronous tasks. There are strict text definitions for this part, but the purpose of this article is to thoroughly understand the execution mechanism with the minimum learning cost, so we use a map to illustrate:

If the content to be expressed in the map is expressed in words:

  • Synchronous and asynchronous tasks enter different execution "places", synchronously enter the main thread, and asynchronously enter the Event Table and register functions.
  • When the specified thing is completed, Event Table will move this function into Event Queue.
  • The task in the main thread is empty after execution, and will go to the Event Queue to read the corresponding function and enter the main thread for execution.
  • The above process will be repeated continuously, which is often called Event Loop (event loop).

We can't help but ask, how do we know that the main thread execution stack is empty? The js engine has a monitoring process, which will continuously check whether the main thread execution stack is empty. Once it is empty, it will go to the Event Queue to check whether there are functions waiting to be called.

Having said so much text, it is more straightforward to directly write a piece of code:

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');

The above is a simple ajaxrequest code:

  • Ajax enters the Event Table and registers a callback function success.
  • execute console.log('代码执行结束').
  • The ajax event is completed, and the callback function successenters the Event Queue.
  • The main thread reads the callback function from the Event Queue successand executes it.

I believe that through the above text and code, you have a preliminary understanding of the execution sequence of js. Next, let's study the advanced topic: setTimeout.

3. Love and hate setTimeout

There is no need to say more about the famous one setTimeout. Everyone’s first impression of him is that asynchronous execution can be delayed. We often implement a delay of 3 seconds in this way:

setTimeout(() => {
    console.log('延时3秒');
},3000)

Gradually, setTimeoutmore and more places are used, and problems also arise. Sometimes the delay is clearly written for 3 seconds, but the function is actually executed after 5 or 6 seconds. What's the matter?

Let's look at an example first:

setTimeout(() => {
    task();
},3000)
console.log('执行console');

According to our previous conclusion, setTimeoutit is asynchronous, and console.logthis synchronous task should be executed first, so our conclusion is:

//执行console
//task()

To verify, the result is correct!
Then we modify the previous code:

setTimeout(() => {
    task()
},3000)

sleep(10000000)

At first glance, it’s almost the same, but when we execute this code in chrome, we find that the execution task()time of the console is much more than 3 seconds, and the agreed delay is 3 seconds. Why does it take so long now?

At this time we need to re-understand setTimeoutthe definition. Let's first talk about how the above code is executed:

  • task()Enter the Event Table and register, and the timer starts.
  • Execute sleepthe function, very slowly, very slowly, and the timing continues.
  • When 3 seconds are up, the timing event timeoutis completed and task()enters the Event Queue, but sleepit is too slow, and it has not been executed yet, so we have to wait.
  • sleepFinally, the execution is over, task()and finally enters the main thread execution from the Event Queue.

After the above process is completed, we know that this function is to add the task to be executed (in this example) to the Event Queue setTimeoutafter the specified time, and because it is a single-threaded task, it needs to be executed one by one. If the previous tasks need task()If the time is too long, then you can only wait, resulting in a real delay time much longer than 3 seconds.

We often encounter setTimeout(fn,0)such code, what does it mean to execute after 0 seconds? Can it be executed immediately?

The answer is no, setTimeout(fn,0)the meaning is to specify a task to be executed at the earliest available idle time of the main thread, which means that there is no need to wait for many seconds, as long as the main thread executes all the synchronization tasks in the stack and the stack is empty Just do it right away. for example:

//代码1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},0);
//代码2
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},3000);

The output of Code 1 is:

//先执行这里
//执行啦

The output of Code 2 is:

//先执行这里
// ... 3s later
// 执行啦

What setTimeoutI want to add is that even if the main thread is empty, 0 milliseconds is actually unreachable. According to HTML standards, the minimum is 4 milliseconds. Interested students can learn by themselves.

4. Hate and love setInterval

Having finished the above setTimeout, of course you can't miss its twin brother setInterval. They are similar, except that the latter is a cyclic execution. For the execution sequence, setIntervalthe registered function will be placed into the Event Queue at specified intervals, and if the previous tasks take too long, you also need to wait.

The only thing to note is that, for setInterval(fn,ms), we already know that it mswill not be executed every second , but will enter the Event Queue fnevery second . Once the execution time of the callback function exceeds the delay time , there will be no time interval at all . Please read this sentence carefully.msfnsetIntervalfnms

5.Promise与process.nextTick(callback)

We have already studied the traditional timer, and then we explore the performance of the Promisetimer process.nextTick(callback).

PromiseThe definition and function of this article will not go into details. Readers who don’t understand can learn about Promise by Mr. Ruan Yifeng . And process.nextTick(callback)similar to the "setTimeout" of the node.js version, the callback callback function is called in the next cycle of the event loop.

Let's get to the point. In addition to the generalized synchronous tasks and asynchronous tasks, we have a more refined definition of tasks:

  • macro-task (macro task): including the overall code script, setTimeout, setInterval
  • micro-task (micro-task): Promise, process.nextTick

Different types of tasks will enter the corresponding Event Queue, for example setTimeoutand setIntervalwill enter the same Event Queue.

The order of the event loop determines the execution order of the js code. After entering the overall code (macro task), start the first cycle. All microtasks are then executed. Then start from the macro task again, find one of the task queues to be executed, and then execute all the micro tasks. It sounds a bit convoluted, let's use a piece of code at the beginning of the article to illustrate:

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

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');

  • This code enters the main thread as a macro task.
  • Encountered first setTimeout, then register its callback function and distribute it to the macro task Event Queue. (The registration process is the same as above, and will not be described below)
  • When it is encountered next Promise, new Promiseit is executed immediately, and thenthe function is distributed to the microtask Event Queue.
  • Encountered console.log(), execute immediately.
  • Well, the overall code script is executed as the first macro task, let's see what micro tasks are there? We found that thenin the microtask Event Queue, execute.
  • Ok, the first round of the event loop is over, we start the second round of the loop, of course, starting from the macro task Event Queue. We found setTimeoutthe corresponding callback function in the macro task Event Queue and executed it immediately.
  • Finish.

The relationship between event loop, macro task and micro task is shown in the figure:

Let's analyze a more complex piece of code to see if you have really mastered the execution mechanism of js:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

The analysis of the first round of event loop process is as follows:

  • The overall script enters the main thread as the first macro task, and when encountered console.log, outputs 1.
  • Encountered setTimeout, its callback function is distributed to the macro task Event Queue. Let's record it as setTimeout1.
  • Encountered process.nextTick(), its callback function is distributed to the microtask Event Queue. We mark as process1.
  • Encountered Promise, new Promiseexecute directly, and output 7. thenDistributed to the microtask Event Queue. We mark as then1.
  • Encountered again setTimeout, its callback function is distributed to the macro task Event Queue, we record it assetTimeout2

  • The above table is the situation of each Event Queue at the end of the first round of event loop macro tasks, and 1 and 7 have been output at this time.

  • We found process1and then1two microtasks.

  • Execute process1, output 6.
  • Execute then1and output 8.

Well, the first round of the event loop is officially over, and the result of this round is to output 1, 7, 6, 8. Then the second round of time loop setTimeout1starts from the macro task:

  • First output 2. Next, it is encountered process.nextTick(), and it is also distributed to the microtask Event Queue, which is recorded as process2. new PromiseImmediately execute the output 4, thenand also distribute it to the microtask Event Queue, denoted as then2.

  • At the end of the second round of event loop macrotasks, we found that there are process2two then2microtasks that can be executed.
  • Output 3.
  • output 5.
  • The second round of event loop ends, and the second round outputs 2, 4, 3, 5.
  • The third round of event loop starts, and only setTimeout2 is left at this time, execute.
  • Output 9 directly.
  • Will process.nextTick()be distributed to the microtask Event Queue. mark as process3.
  • Execute directly new Promiseand output 11.
  • Will thenbe distributed to the microtask Event Queue, recorded as then3.

  • The third round of event loop macro task execution ends, process3and two micro tasks are executed then3.
  • Output 10.
  • Output 12.
  • The third round of event loop ends, and the third round outputs 9, 11, 10, 12.

The entire code, a total of three event loops, the complete output is 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12.
(Please note that the event monitoring in the node environment depends on libuv and the front-end environment are not exactly the same, and the output order may be wrong)

6. Write at the end

(1) asynchronous js

We said from the very beginning that javascript is a single-threaded language. No matter what kind of so-called asynchrony implemented by new framework and new syntactic sugar, it is actually simulated by a synchronous method. It is very important to firmly grasp the single-threaded language.

(2) Event loop Event Loop

The event loop is a method for js to achieve asynchrony, and it is also the execution mechanism of js.

(3) Execution and operation of javascript

There is a big difference between execution and operation. JavaScript executes in different environments, such as node, browser, Ringo, etc., in different ways. The operation mostly refers to the javascript parsing engine, which is unified.

(4)setImmediate

There are many types of micro-tasks and macro-tasks, such as, setImmediateetc., the execution has something in common, and interested students can learn by themselves.

(5) last last

  • JavaScript is a single-threaded language
  • Event Loop is the execution mechanism of javascript

Firmly grasp the two basic points, focus on earnestly learning javascript, and realize the great dream of becoming a front-end master as soon as possible!

Note: This article is transferred from Nuggets, author: ssssyoki, original link: https://juejin.im/post/59e85eebf265da430d571f89
 

Guess you like

Origin blog.csdn.net/wh_xmy/article/details/104966317