前端进阶:看完就清楚的 JS EvenetLoop(事件循环)
准备
我们都知道,JS 是单线程的
,需要执行完当前任务后,才能执行下一个任务。这就类似于你打饭,前面一个人打了饭,后面的人才能打。不然前面的人在那卡 bug,你后面的人就被阻塞打不聊饭,就只有饿肚子。
同理,在 JS 主线程的执行栈上,也是需要一个任务一个任务的执行。如果遇到计算量大、耗时久的任务,那么后续的任务就会被阻塞
,造成响应不及时、页面卡顿等问题。因此,JS 有了它独特的生态:EventLoop(事件循环)
JS 任务分类
JS 的任务分为同步任务、异步任务
,而异步任务又分为宏任务、微任务
比如:
- Promise.then():微任务
- setTimeOut:宏任务
- async、await:微任务
- setInterval:宏任务
- ...(还有很多不常用的)
那么他们是如何执行的呢?
EventLoop
EventLoop 流程
EventLoop 的流程可以简单来说可以概况为:同步任务 ➡ 微任务 ➡ 宏任务
具体而言:
- 执行
同步任务
- 同步任务执行完毕后,
去微任务队列
,看有没有需要执行的微任务
- 如果有,则把当前微任务队列里面
所有的微任务推入主线程的执行栈
,执行微任务,执行完毕,去宏任务队列。 - 如果没有,则去宏任务队列
- 看有没有需要执行的宏任务,如果有,则推入主线程执行栈执行(
只取一个宏任务
)。 - 当前这个宏任务执行完后,又去微任务队列,看有没有需要执行的微任务。如果有,
全部
推入执行栈。执行完毕后,又回到宏任务队列,取出当前一个宏任务,推入执行栈执行。执行完又去微任务队列,依次循环。
图解如下:
例题讲解
直接看代码,很快就懂了
第一题:
console.log(1)
setTimeout(() => {
console.log(2)
}, 0);
Promise.resolve().then(() => {
console.log(3)
})
console.log(4)
输出顺序: 1 4 3 2
分析:
- JS 按顺序执行同步任务,所以先执行
console.log(1)
和console.log(4)
- 执行完同步任务,看微任务队列,有没有需要执行的微任务,
此时微任务队列只有一个微任务 Promise.resolve().then(() => console.log(3))
,所以推入执行栈,执行console.log(3)
- 当前微任务执行完,回到
宏任务队列,此时有一个 setTimeout
,推入执行栈,执行console.log(2)
(注意:每次去宏任务队列时,都只取一个宏任务出来
)
第二题:
//S1
setTimeout(() => {
console.log(1);
//S4
setTimeout(() => {
//M2
Promise.resolve().then(() => {
console.log(9);
});
}, 0);
//M3
Promise.resolve().then(() => {
console.log(7);
});
}, 0);
console.log(2);
//M1
Promise.resolve().then(() => {
console.log(3);
});
//S2
setTimeout(() => {
console.log(8);
//S5
setTimeout(() => {
console.log(5);
}, 0);
}, 0);
//S3
setTimeout(() => {
//M6
Promise.resolve().then(() => {
console.log(4);
});
}, 0);
console.log(6);
输出顺序: 2 6 3 1 7 8 4 9 5
分析:
为了方便我们分析,我给每个宏任务取个别名 S几,微任务取别名 M几
- 首先,执行所有的同步任务,也就是
console.log(2)
和console.log(6)
,所以先打印2 6
- 宏任务和微任务依次进入各自的队列。此时的宏任务队列、微任务队列分别是:
-
宏任务队列:S1、S2、S3
-
微任务队列:M1
那么此时,取出所有的微任务,也就是 M1,执行
console.log(3)
然后去宏任务队列,取出一个宏任务(队列先进先出,所以先取出 S1),执行
console.log(1)
,执行的过程中又遇到了 S4,此时 S1 还没执行完,那么 S4 入宏任务队列,然后遇到 M3,执行console.log(7)
,此时 S1 执行完毕。此时打印的结果为:2 6 3 1 7
- 执行完前面两步后,宏任务队列、微任务队列分别是:
-
宏任务队列:S2、S3、S4
-
微任务队列:
此时,微任务队列没有微任务,那么从宏任务队列取出 S2,执行
console.log(8)
,然后遇到 S5,此时 S2 还没执行完,所以 S5 推入宏任务队列。此时打印的结果为:2 6 3 1 7 8
。
- 经过第三步后,宏任务队列、微任务队列分别是:
-
宏任务队列:S3、S4、S5
-
微任务队列:
同样,微任务队列没有任务,则从宏任务队列中,取出 S3 执行,然后遇到 微任务 M6,M6 推入微任务队列。
此时宏任务队列、微任务队列分别是:
- 宏任务队列:S4、S5
- 微任务队列:M6
那同样的,取出 M6,执行
console.log(4)
,此时打印结果是:2 6 3 1 7 8 4
- 经过第四步后,此时宏任务队列、微任务队列分别是:
- 宏任务队列:S4、S5
- 微任务队列:
取出 S4,执行 S4,遇到 M2,M2 入微任务队列。
- 此时宏任务队列、微任务队列分别是:
- 宏任务队列:S5
- 微任务队列:M2
取出 M2,执行 console.log(9)
,此时打印结果是:2 6 3 1 7 8 4 9
- 此时宏任务队列、微任务队列分别是:
- 宏任务队列:S5
- 微任务队列:
取出 M5,执行 console.log(5)
,此时打印结果是:2 6 3 1 7 8 4 9 5
总结
需要注意的点是:每次从宏任务队列里面取宏任务时,只取一个;而从微任务队列里面取微任务时,取全部。
结语
以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论。
转载自:https://juejin.cn/post/7231759334068568123