JavaScript 事件循环:宏任务与微任务
JavaScript 是单线程的,却能处理定时器、网络请求和用户交互,靠的就是事件循环(Event Loop)。理解它,是读懂异步代码执行顺序的关键。
一、基本模型
一次事件循环大致分为三步:
- 从宏任务队列取出一个任务执行(第一次是整段脚本本身)。
- 执行完后,清空微任务队列——包括执行过程中新加入的微任务。
- 必要时进行页面渲染,然后进入下一轮。
- 常见宏任务:
setTimeout、setInterval、I/O、UI 事件、MessageChannel - 常见微任务:
Promise.then/catch/finally、queueMicrotask、MutationObserver
二、来看一道经典题
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => {
console.log('3');
setTimeout(() => console.log('4'), 0);
});
(async () => {
console.log('5');
await null;
console.log('6');
})();
console.log('7');
输出顺序是:1 5 7 3 6 2 4。
逐步分析
- 同步代码依次输出
1;setTimeout回调进入宏任务队列;then回调进入微任务队列。 - async 函数体在
await之前是同步执行的,输出5;await之后的代码相当于一个then,进入微任务队列。 - 同步输出
7,本轮脚本结束。 - 清空微任务:输出
3(并注册一个新宏任务 4),再输出6。 - 执行下一个宏任务输出
2,再下一个输出4。
三、实际开发中的启示
- 微任务会“插队”到渲染之前:在微任务里无限递归会卡死页面,因为渲染永远等不到。
- 想让出主线程,应使用
setTimeout或scheduler.postTask,而不是 Promise。 - 批量更新 DOM:Vue 的
nextTick正是借助微任务,在同一轮内合并多次数据变更。
小结
一轮循环 = 一个宏任务 + 全部微任务 + 可能的渲染。抓住这个模型,绝大多数执行顺序问题都能迎刃而解。
← 返回首页