2023-06-08 16:38:04 +08:00
|
|
|
|
---
|
|
|
|
|
title: 12-事件循环机制、宏任务和微任务
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<ArticleTopAd></ArticleTopAd>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 浏览器的事件循环机制(重要)
|
|
|
|
|
|
|
|
|
|
![image-20230608154453933](https://img.smyhvae.com/image-20230608154453933.png)
|
|
|
|
|
|
|
|
|
|
执行顺序如下:
|
|
|
|
|
|
|
|
|
|
- 同步任务:进入主线程后,立即执行。
|
|
|
|
|
|
|
|
|
|
- 异步任务:会先进入 Event Table;等时间到了之后,再进入 任务队列 (Event Queue),然后排队(为什么要排队?因为同一时间,JS 只能执行一个任务)。比如说,`setTimeout(()=> {}, 1000)`这种定时器任务,需要等一秒之后再进入 Event Queue。
|
|
|
|
|
|
|
|
|
|
- 当主线程的任务执行完毕之后,此时主线程处于空闲状态,于是会去读取 Event Queue 中的任务队列,如果有任务,则进入到主线程去执行。
|
|
|
|
|
|
2023-06-11 17:19:39 +08:00
|
|
|
|
## Node.js 事件循环机制
|
|
|
|
|
|
|
|
|
|
浏览器的 EventLoop 依据的是 HTML5 规范。而 Node.js 的 EventLoop 是由Node.js底层的 libuv 规定的。 libuv是一个专注于异步IO的跨平台库。
|
|
|
|
|
|
|
|
|
|
Node.js的事件循环中,有六个队列。其中,微任务有两个队列,宏任务有四个队列。
|
|
|
|
|
|
|
|
|
|
一、微任务队列:
|
|
|
|
|
|
|
|
|
|
- 顺序1:next tick queue。比如:process.nextTick
|
|
|
|
|
- 顺序2:other queue。比如:Promise的then回调、queueMicrotask
|
|
|
|
|
|
|
|
|
|
二、宏任务队列:
|
|
|
|
|
|
|
|
|
|
- 顺序3:timer queue。比如:setTimeout、setInterval
|
|
|
|
|
- 顺序4:poll queue。比如:IO事件
|
|
|
|
|
- 顺序5:check queue。比如:setImmediate
|
|
|
|
|
- 顺序6:close queue。比如:close事件
|
|
|
|
|
|
|
|
|
|
参考链接:
|
|
|
|
|
|
|
|
|
|
- 【荐】浏览器及nodeJS中的EventLoop事件循环机制:https://www.cnblogs.com/weiyongchao/p/13766429.html
|
|
|
|
|
- 浏览器和Node 事件循环的区别:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/26
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-06-08 16:38:04 +08:00
|
|
|
|
## 宏任务和微任务
|
|
|
|
|
|
|
|
|
|
JS中的任务分为同步任务、异步任务。
|
|
|
|
|
|
|
|
|
|
JS中的异步任务分为宏任务(macrotask)、微任务(microtask)。在早期,异步任务中只有宏任务,没有微任务。后来的语言标准中,推出了“微任务”,因为**希望微任务能够尽早执行**。
|
|
|
|
|
|
|
|
|
|
### 宏任务、微任务分类
|
|
|
|
|
|
|
|
|
|
事件循环的队列中,有两个队列。
|
|
|
|
|
|
|
|
|
|
1、**宏任务队列**,包含这些任务:
|
|
|
|
|
|
|
|
|
|
- ajax 网络请求
|
2023-06-17 23:56:31 +08:00
|
|
|
|
- setTimeout、setInterval
|
2023-06-08 16:38:04 +08:00
|
|
|
|
- DOM事件
|
|
|
|
|
- UI渲染
|
|
|
|
|
- I/O文件读写操作。
|
|
|
|
|
|
|
|
|
|
2、**微任务队列**,包含这些任务:
|
|
|
|
|
|
|
|
|
|
- Promise的then回调
|
|
|
|
|
- Mutation Observer API:监听DOM节点变化。
|
|
|
|
|
- queueMicrotask():可直接将某个任务加入到微任务队列中。
|
|
|
|
|
|
|
|
|
|
在执行一个 Promise 对象时,当走完 resolve() 进入 fulfilled状态后,会立刻把 `.then()`里面的代码加入到**微任务队列**当中。
|
|
|
|
|
|
|
|
|
|
### 任务的执行顺序
|
|
|
|
|
|
|
|
|
|
JS中的任务执行顺序:**同步任务 --> 微任务 --> 宏任务**。
|
|
|
|
|
|
|
|
|
|
在执行任何一个宏任务之前(不是队列,是一个宏任务),都会**先查询微任务队列中是否还有任务需要执行**:
|
|
|
|
|
|
|
|
|
|
- 当前宏任务执行之前,必须要保证微任务队列是空的。
|
|
|
|
|
- 如果微任务队列不为空,那就优先执行微任务队列中的任务。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 任务执行顺序的面试题
|
|
|
|
|
|
|
|
|
|
实际开发中,基本不会出现下面这些题目,因为很多时候我们无法精准控制异步任务的执行顺序。但是它们在面试中出现的频率特别高,因为熟悉这些思维训练,有利于考察我们对JS单线程、事件循环机制、宏任务和微任务等原理的掌握程度。
|
|
|
|
|
|
2023-06-08 17:11:24 +08:00
|
|
|
|
### 题 1:宏任务和微任务的执行顺序
|
2023-06-08 16:38:04 +08:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
// 宏任务
|
|
|
|
|
console.log('setTimeout');
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
|
resolve();
|
|
|
|
|
console.log('promise1'); // 同步任务
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
// 微任务
|
|
|
|
|
console.log('promise then');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('同步任务'); // 同步任务
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
打印结果:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
promise1
|
|
|
|
|
同步任务
|
|
|
|
|
promise then
|
|
|
|
|
setTimeout
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上方代码执行的顺序依次是:**同步任务 --> 微任务 --> 宏任务**。
|
|
|
|
|
|
2023-06-08 17:11:24 +08:00
|
|
|
|
### 题 2:在宏任务中嵌套了微任务
|
2023-06-08 16:38:04 +08:00
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
resolve();
|
|
|
|
|
console.log('setTimeout'); // 宏任务
|
|
|
|
|
}, 0);
|
|
|
|
|
console.log('promise1');
|
|
|
|
|
}).then((res) => {
|
|
|
|
|
// 微任务
|
|
|
|
|
console.log('promise then');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('同步任务');
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
打印结果:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
promise1
|
|
|
|
|
同步任务
|
|
|
|
|
setTimeout
|
|
|
|
|
promise then
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上方代码解释:在执行宏任务的**过程中**,创建了一个微任务。但是需要**先把当前这个宏任务执行完**,再去**创建并执行**微任务。
|
2023-06-08 17:11:24 +08:00
|
|
|
|
|
|
|
|
|
### 题3:综合题
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
console.log("script start")
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
console.log("setTimeout1");
|
|
|
|
|
new Promise(resolve => {
|
|
|
|
|
resolve();
|
|
|
|
|
}).then(() => {
|
|
|
|
|
new Promise(resolve => {
|
|
|
|
|
resolve();
|
|
|
|
|
}).then(() => {
|
|
|
|
|
console.log("then1");
|
|
|
|
|
});
|
|
|
|
|
console.log("then2");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
new Promise(resolve => {
|
|
|
|
|
// 下面这两行代码,即便调换顺序,也不影响打印结果
|
|
|
|
|
console.log("promise1");
|
|
|
|
|
resolve();
|
|
|
|
|
}).then(() => {
|
|
|
|
|
console.log("then3");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
console.log("setTimeout2");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log('同步代码');
|
|
|
|
|
|
|
|
|
|
queueMicrotask(() => {
|
|
|
|
|
console.log("queueMicrotask")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
new Promise(resolve => {
|
|
|
|
|
resolve();
|
|
|
|
|
}).then(() => {
|
|
|
|
|
console.log("then4");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log("script end");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
打印结果:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
// 第一次循环
|
|
|
|
|
script start
|
|
|
|
|
promise1
|
|
|
|
|
同步代码
|
|
|
|
|
script end
|
|
|
|
|
|
|
|
|
|
// 第二次循环
|
|
|
|
|
then3
|
|
|
|
|
queueMicrotask
|
|
|
|
|
then4
|
|
|
|
|
|
|
|
|
|
// 第三次循环
|
|
|
|
|
setTimeout1
|
|
|
|
|
then2
|
|
|
|
|
then1
|
|
|
|
|
|
|
|
|
|
// 第四次循环
|
|
|
|
|
setTimeout2
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 题4:async await 题目
|
|
|
|
|
|
|
|
|
|
代码举例:
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
console.log('script start')
|
|
|
|
|
|
|
|
|
|
async function async2() {
|
|
|
|
|
console.log('async2')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function async1() {
|
|
|
|
|
console.log('async1 start')
|
|
|
|
|
await async2();
|
|
|
|
|
console.log('async1 end')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
console.log('setTimeout')
|
|
|
|
|
}, 0)
|
|
|
|
|
|
|
|
|
|
async1();
|
|
|
|
|
|
|
|
|
|
new Promise(resolve => {
|
|
|
|
|
console.log('promise1')
|
|
|
|
|
resolve();
|
|
|
|
|
}).then(function () {
|
|
|
|
|
console.log('then1')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
console.log('script end');
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
打印结果:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
script start
|
|
|
|
|
async1 start
|
|
|
|
|
async2
|
|
|
|
|
promise1
|
|
|
|
|
script end
|
|
|
|
|
|
|
|
|
|
async1 end
|
|
|
|
|
then1
|
|
|
|
|
|
|
|
|
|
setTimeout
|
|
|
|
|
```
|
|
|
|
|
|
2023-06-17 23:56:31 +08:00
|
|
|
|
## 赞赏作者
|
2023-06-08 17:11:24 +08:00
|
|
|
|
|
2023-06-17 23:56:31 +08:00
|
|
|
|
创作不易,你的赞赏和认可,是我更新的最大动力:
|
|
|
|
|
|
|
|
|
|
![](https://img.smyhvae.com/20220401_1800.jpg)
|
2023-06-08 17:11:24 +08:00
|
|
|
|
|