事件循环原来这么简单!

做前端开发多年了,一直对浏览器的事件循环似懂非懂,怎么说呢?对于事件循环的学习,基本都是直接在网上看的碎片化的解释,看一遍背一遍,背一遍忘一遍,似乎总是模模糊糊的。对于一些比较难的面试题,总有些地方的执行顺序是混乱的。不过最近看了一套课程,对于浏览器事件循环的讲解非常清晰,学习后感觉自己真的懂了,于是为了验证学习的成果,我让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在下一轮循环执行,微任务产生的微任务又在当前这轮循环执行,简直就是脑经急转弯,让我晕头转向)

三个部分:浏览器渲染主线程,消息队列,和其他线程(处理异步任务)

  1. 渲染主线程会不断去查看消息队列里是否有需要执行的任务,有就把任务从消息队列里拿出来执行,没有就进入等待状态。
  2. 有多条消息队列,消息队列有优先级,微队列优先级最高
  3. 每一个任务都有一个任务类型,同一类型的任务必须要在同一队列
  4. 每条消息队列里任务没有优先级,先进先出
  5. 渲染主线程会把异步任务交给其他线程处理
  6. 当异步任务完成时,其他线程会把其回调函数包装成任务,放入对应的消息队列里
  7. 消息列表有很多条,我们先化简为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端全流程系统。

相关推荐
gf13211111 小时前
python_【更新已发送的消息卡片】
java·前端·python
一点一木1 小时前
2026 终端 AI 编码 Agent 六大工具深度横评
前端·人工智能·claude
Highcharts.js1 小时前
Highcharts React v5升级三问|最大的升级方向是什么?需要注意什么?有什么优化?
前端·javascript·react.js·前端框架·highcharts·大数据渲染·前端性能
马玉霞1 小时前
vue web端页面组件展示
前端·vue.js
代码煮茶1 小时前
Vue3 权限系统实战 | 从 0 搭建完整 RBAC 权限管理
前端·javascript·vue.js
IT_陈寒1 小时前
用了Vue的动态组件之后,我被坑得找不着北
前端·人工智能·后端
阳火锅2 小时前
💡 告别类名地狱!Tailwind CSS 语义化转换神器来了
前端·css·vue.js
ricardo19732 小时前
Core Web Vitals 全解:LCP / INP / CLS 逐个击破
前端
VillenK2 小时前
版本依赖问题:vite-plugin-dts@3.1.0 与 jiti 的兼容性
前端·typescript·vite