Home  >  Article  >  Web Front-end  >  How to understand the Javascript event loop? (pictures and text)

How to understand the Javascript event loop? (pictures and text)

不言
不言Original
2018-09-08 17:46:141913browse

This article brings you how to understand the Javascript event loop? (Pictures and text), it has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Stacks and queues

Access data in computer memory. The basic data structures are divided into stacks and queues.

Stack (Stack) is a last-in-first-out data structure. Note that sometimes the stack is called a "stack", but the "heap" is another complex data structure. It is completely different from the stack. They are two different things. The characteristic of the stack is that operations are only performed on one end. Generally speaking, there are only two stack operations: push and pop. The first data pushed onto the stack is always the last one to come out.

How to understand the Javascript event loop? (pictures and text)

Queue (Queue) is similar to a stack, but it is a first-in, first-out data structure. The operation of inserting data is performed from one end of the queue, and The deletion operation is on the other side.

How to understand the Javascript event loop? (pictures and text)

The popular metaphor is that a stack is like an erected bucket. The data put into the stack first will be placed at the bottom of the bucket. The data is taken out one by one at the mouth of the bucket, so the data put into the stack first is always the last one to be taken out. The queue is like a water pipe. The first data put into the queue will be the first to flow out from the other end of the queue. This is the biggest difference between them.

In JavaScript, the execution of a function is a typical process of pushing and popping onto the stack:

function fun1() {
    function fun2() {
        function fun3() {
            console.log('do it');
        }
        fun3();
    }
    fun2();
}
fun1();

When the program is executed, fun1, fun2, and fun3 are first pushed onto the stack in sequence, and then When calling a function, fun3 is called (popped) first, then fun2 and fun1. Just imagine, if fun1 is popped off the stack first, then functions fun2 and fun3 will be lost.

Single-threaded and asynchronous

In the language of JavaScript, the program is single-threaded and has only one main thread. Why is this? Because it is not difficult to imagine that JavaScript was originally designed to be a scripting language that runs in the browser. If it is designed to be multi-threaded and two threads modify the DOM at the same time, whose decision will be used? Therefore, JavaScript is single-threaded. In one thread, the code will go down sentence by sentence until the program is finished. If there are more time-consuming operations in the middle, you can only wait.

The single-threaded design makes the execution efficiency of the language very poor. In order to take advantage of the performance of multi-core CPUs, the JavaScript language supports asynchronous code. When there are more time-consuming operations, the task can be written for asynchronous execution. When a When the asynchronous task has not been executed, the main thread will suspend the asynchronous task and continue to execute the subsequent synchronous code. Then, look back and execute it again if there is an asynchronous task that has finished running.

This way of executing code is actually very consistent with many scenes in our lives. For example, Xiao Ming comes home from get off work. He is very thirsty and wants to boil water for tea. If it is a synchronous execution method, he will boil water. , when the water is not boiling, Xiao Ming waits like a fool, waiting for the water to boil before making tea; if it is executed asynchronously, Xiao Ming starts to boil the water first, and then goes to do other things, such as watching TV or listening to music. Wait for the water to boil before making tea. Obviously the second asynchronous method is more efficient.

What are the common asynchronous operations? There are many, we can list a few common ones:

  • Ajax

  • DOM event operations

  • setTimeout

  • Promise’s then method

  • Node’s reading file

Let’s first Let’s take a look at a piece of code:

//示例1
console.log(1);
setTimeout(function () {
    console.log(2);
}, 1000);
console.log(3);

This code is very simple. Put it in the browser and execute it. The result is as follows:

1
3
2

Because the setTimeout function delays execution by 1000 milliseconds, it outputs 1 first. and 3, and 2 is output after 1000 milliseconds, which is very logical.

Let’s change the code slightly and change the delay time of setTimeout to 0:

//示例2
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0); //0毫秒,不延时
console.log(3);

Running results:

1
3
2

Why is the delay of 0 milliseconds still the last output of 2 ? Don’t worry, let’s look at a piece of code:

//示例3
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0);
Promise.resolve().then(function(){
    console.log(3);
});
console.log(4);

Running results:

1
4
3
2

If you can write the results of the above three pieces of code correctly and explain why it is output like this, explain You understand the JavaScript event loop very clearly. If you can't explain it, let's talk about what's going on here. It's actually very interesting.

How is javascript executed?

Let’s briefly talk about the basic data structure at the beginning. Does it have anything to do with the event loop we are talking about now? Of course there is. The first thing to make clear is that javascript code is all executed on the stack, whether it is synchronous code or asynchronous code, this must be clear.

The code is generally divided into synchronous code and asynchronous code. In fact, asynchronous code can be further divided into two categories: Macro tasks and Micro tasks.

Don’t worry about what macro tasks and micro tasks are. Often such high-level terminology is not conducive to our understanding. Let’s first think of it this way: macro means macro and big; micro means micro and small. .

Javascript is an interpreted language, and its execution process is as follows:

  1. 从上到下依次解释每一条js语句

  2. 若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列里

  3. 开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时栈清空了

  4. 回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中

  5. 再返回第3步,直到异步队列都清空,程序运行结束

语言描述的费劲,不如看图:

How to understand the Javascript event loop? (pictures and text)

通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。

明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。

浏览器中的事件环

聊了这么多,我们好像还没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?

注意!微任务和宏任务的执行方式在浏览器和Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。

在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。

接下来我们借用阮一峰老师的一张图来说明:

How to understand the Javascript event loop? (pictures and text)

其实,以上这张图示我们可以再将它细化一点,这个图上的异步队列只画了一个,也就是说没有区分微任务队列和宏任务队列。我们可以脑补一下,在此图上多加一个微任务队列,当javascript执行时再多加一个判断,如果是微任务就加到微任务队列里,宏任务就加到宏任务队列里,在清空队列时,浏览器总会优先清空“微任务”。这样就把浏览器的事件环撤底说全了。

最后来一个大考,以下代码的运行结果是什么:

<script>
    setTimeout(function () {
        console.log(1);
        Promise.resolve().then(function () {
            console.log(2);
        });
    });
    setTimeout(function () {
        console.log(3);
    });
    Promise.resolve().then(function () {
        console.log(4);
    });
    console.log(5);
</script>

将此代码拷到chrome中跑一下,结果是:

5
4
1
2
3

不妨我们试着分析一下为什么是这个结果,首先输出5,因为console.log(5)是同步代码,这没什么可说的。

之后将前两个setTimeout和最后一个Promise放入异步队列,注意它们的区分,此时执行完了同步代码之后发现微任务和宏任务队列中都有代码,按浏览器的事件环机制,优先执行微任务,此时输出4。

然后执行宏任务队列里的第一个setTimeout,输出1。

此时,setTimeout中又有一个Promise,放入微任务队列。

再次清空微任务队列,输出2。

最后宏任务队列里还有最后一个setTimeout,输出3。

Node中的事件环

而Node中的事件环又和浏览器有些许的不同,在node.js的官方文档中有专门的描述,其中文档中有一张图,详细的说明了它的事件环机制,我们把它拿出来:

How to understand the Javascript event loop? (pictures and text)

可以看到,node.js中的事件环机制分为了6个阶段,其中最重要的3个阶段我在上面做了注明:

  • timer阶段,指的就是setTimeout等宏任务

  • poll轮询阶段,如读取文件等宏任务

  • check阶段,setImmediate宏任务

图中每一个阶段都代表了一个宏任务队列,在Node事件环中,微任务的运行时机是在每一个“宏任务队列”清空之后,在进入下一个宏任务队列之间执行。这是和浏览器的最大区别。

还是用代码说话吧,有一道经典的Node.js事件环面试题:

const fs = require('fs');

fs.readFile('./1.txt', (err, data) => {
    setTimeout(() => {
        console.log('timeout');
    });
    setImmediate(() => {
        console.log('immediate');
    });
    Promise.resolve().then(() => {
        console.log('Promise');
    });
});

运行结果:

Promise
immediate
timeout

代码并不复杂,首先使用fs模块读取了一个文件,在回调的内部有两个宏任务和一个微任务,微任务总是优于宏任务执行的,因此先输出Promise。

但是之后的区别为什么先输出immdiate?原因就在于fs读取文件的宏任务在上图中的第4个轮询阶段,当第4个阶段清空队列之后,就该进入第5个check阶段,也就是setImmediate这个宏任务所在的阶段,而不会跳回第1个阶段,因此先输出immedate。

尾巴

最后总结一下,分析完浏览器和Node的事件环发现它们并不简单,但只要记住了它们之间的区别就可以分析出结果。

浏览器事件环是运行完一个宏任务马上清空微任务队列
Node事件环是清空完一个阶段的宏任务队列之后再清空微任务队列

最后,总结一下常见的宏任务和微任务:

宏任务 微任务
setTimeout Promise的then方法
setInterval process.nextTick
setImmediate MutationObserver
MessageChannel

相关推荐:

细说JavaScript事件循环机制-第二讲

javascript实现线程以及事件循环详解

The above is the detailed content of How to understand the Javascript event loop? (pictures and text). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn