做前端开发多年了,一直对浏览器的事件循环似懂非懂,怎么说呢?对于事件循环的学习,基本都是直接在网上看的碎片化的解释,看一遍背一遍,背一遍忘一遍,似乎总是模模糊糊的。对于一些比较难的面试题,总有些地方的执行顺序是混乱的。不过最近看了一套课程,对于浏览器事件循环的讲解非常清晰,学习后感觉自己真的懂了,于是为了验证学习的成果,我让kimi给我出了一道题目。

于是我经过思考在纸上写下答案:1 → 2 → 8 → 3→ 6 → 5 → 7 → 4
但是查看Kimi给的答案是: 1 → 2 → 8 → 6 → 3 → 5 → 7 → 4

看着kimi给我的答案,我陷入了迷茫(说实话我有点看不懂)。
于是我去问豆包:1 → 8 → 2 → 6 → 7 → 3 → 5 → 4

我更加迷茫,但是我知道豆包肯定是错的,因为8的位置很明显不对。
这时,我恍然大悟,我的答案不一定是错的,因为AI有可能是错的。我去问了最靠谱的谷歌浏览器:

看到答案的那一刻,好开心,感觉自己真的懂了事件循环。
下面把我理解的浏览器的"事件循环"简单说一下:
1.首先要把之前的微任务和宏任务忘记,没有微任务也没有宏任务
2.忘记先宏任务后微任务(之前我就是对这里很不清晰 ,看一遍忘一遍,什么微任务在这轮循环执行,setTimeout在下一轮循环执行,微任务产生的微任务又在当前这轮循环执行,简直就是脑经急转弯,让我晕头转向)
三个部分:浏览器渲染主线程,消息队列,和其他线程(处理异步任务)
- 渲染主线程会不断去查看消息队列里是否有需要执行的任务,有就把任务从消息队列里拿出来执行,没有就进入等待状态。
- 有多条消息队列,消息队列有优先级,微队列优先级最高
- 每一个任务都有一个任务类型,同一类型的任务必须要在同一队列
- 每条消息队列里任务没有优先级,先进先出
- 渲染主线程会把异步任务交给其他线程处理
- 当异步任务完成时,其他线程会把其回调函数包装成任务,放入对应的消息队列里
- 消息列表有很多条,我们先化简为3条:微队列,交互队列,计时队列(优先级依次降低)。
promise的任务放在微队列,页面的交互任务放在交互队列,setTimeout放在计时队列。
微任务队列(Microtask Queue)
- Promise.then / catch / finally
- MutationObserver
- queueMicrotask()
- await 后面那部分代码(被包装成微任务)
我觉得最简单的理解就是主线程里的任务执行完成后,先去微队列查看有没有要执行的任务,有就执行,没有就去查看交互队列,有就执行,没有就去查看计时队列。一直无限循环下去

所以我的解题思路是
arduino
console.log(1); // ① 同步 → 打印 1
函数声明,但暂不执行
csharp
async function asyncFunc() { /* ... */ }
setTimeout 直接交给计时线程处理,主线程任务结束。计时线程在0秒后,把回调函数包装成任务,放入计时队列,等待主线程执行。此时什么都不打印,代码继续。
javascript
setTimeout(() => { console.log(4); }, 0);当前不执行,排在后面
调用 asyncFunc()
scss
asyncFunc().then(() => console.log(5));
进入 asyncFunc 函数体(同步部分)
arduino
console.log(2); // ② 同步 → 打印 2
await Promise.resolve(),遇到Promise,渲染主线线程不处理,把它交给其他线程,代码继续往下。
同时Promise.resolve()异步任务结束,其他线程把await 后的代码console.log(3)放入微队列,我们叫它微任务1
链式 Promise.then
javascript
Promise.resolve().then(() => console.log(6))
- 遇到Promise,渲染主线程不处理,把它交给其他线程,代码继续往下
- 同时
Promise.resolve()异步任务结束,其他线程把其then的回调函数() => console.log(6)放入微队列,我们叫它微任务2
最后一行同步
arduino
console.log(8); // ③ 同步 → 打印 8
到这里主线程的同步任务执行结束,输出 1 → 2 → 8。
现在主线程里的任务结束了,他会先去查看微队列里的任务,一看有任务,就把排在最前面的第4步产生的微任务1拿出来执行

微任务1是promise的then的回调函数: console.log(3),执行结束返回一个promise, 主线程此时结束任务执行,并把这个then返回的promise交给其他线程处理,该promise直接resolved,其他线程此时把其then里回调函数() => console.log(5)放入微队列,我们叫它微任务3
arduino
console.log(3);
主线程的同步任务执行结束,输出 1 → 2 → 8->3。
这时主线程再去查看微队列里的任务,把第6步里产生的微任务2拿出来执行。同上一样,执行console.log(6)后,又返回一个新的promise, 其他线程又把其then的回调函数() => console.log(7)放入微队列,我们叫它微任务4

主线程的同步任务执行结束,输出 1 → 2 → 8->3->6。
这时主线程再去查看微对列里的任务,把微任务3拿出来执行:console.log(5),后再把微任务4拿出来执行:console.log(7),这时微队列里没有再加入新的任务了。此时的输出是1 → 2 → 8 → 3→ 6 → 5 → 7

这时主线程的同步任务又清空了,他会先去查看微队列里的任务,一看微队列里没有任务,就去查看交互队列,交互队列也没有任务,再去看计时队列,刚好有一个任务,取出来执行console.log(5)
最后所有队列的任务都清空了,浏览器处于等待状态。
最后的输出就是1 → 2 → 8 → 3→ 6 → 5 → 7 → 4
所以当忘记微任务和宏任务后,记住每种任务在什么时机放入到哪个队列中就可以轻松的给出答案!
团队介绍
「智慧家技术平台-应用软件框架开发」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。