深入浅出 JavaScript 事件循环:从宏任务到微任务
本文最后更新于:2026年2月11日 晚上
深入浅出 JavaScript 事件循环
JS 是单线程的,意思是他一次只能干一件事。但我们在开发里经常要处理异步:发请求、设定时器、监听点击。这看着挺矛盾的,其实全靠 事件循环 (Event Loop) 在背后调度。
1. 为什么是单线程?
JS 最初设计就是为了在浏览器里操作 DOM。如果它是多线程,那问题就大了:
线程 A 说:“我要删掉这个按钮。”
线程 B 说:“我要给这个按钮改个颜色。”
浏览器该听谁的?为了躲开这种麻烦,JS 选了单线程。但这不代表它只能“傻等”同步任务执行完,事件循环就是为了让它能处理异步。
2. 宏任务与微任务
宏任务 (MacroTask)
通常是一些比较重的任务:
setTimeout/setInterval- I/O 操作(读文件、网求请求)
- 整个脚本的初次执行
- UI 渲染
微任务 (MicroTask)
通常是比较轻、需要赶紧处理的任务:
Promise.then()/catch()/finally()queueMicrotask()MutationObserver(监听 DOM 变化)
执行顺序:微任务会“插队”
核心规则只有一条:每执行完一个宏任务,都要去把微任务队列清空,然后再执行下一个宏任务。
看个经典例子:
1 | |
解析一下:
1和4是同步任务,直接输出。setTimeout的回调被扔进宏任务队列。Promise.then的回调被扔进微任务队列。- 同步代码跑完,JS 发现微任务队列里有
3,赶紧执行它。 - 微任务清空了,再去跑宏任务里的
2。
async/await 怎么看?
await 后面的代码,其实可以看作是放在 .then() 里的微任务。
1 | |
再看个例子
1 | |
执行解析:
1、遇到第一个setTimeout放入宏任务队列 【a()】
2、遇到promise 放入 微任务队列 【b()】
3、遇到第二个setTimeout放入宏任务队列 【a(),c()】
4、输出:d,主js代码执行完毕
5、读取微任务队列所有任务,输出 :b
6、读取宏任务队列,取出第一个【a()】,输出:a,同时放入微任务队列:【e, ee】
7、读取微任务队列所有任务,输出 :e ee
8、读取宏任务队列,取出第一个【c()】,输出:c,同时放入微任务队列:【cc】
9、读取微任务队列所有任务,输出 : cc
10、执行完毕