事件循环与宏任务微任务
JS 是单线程:同一时刻只执行一个任务,靠事件循环实现“非阻塞”。执行栈清空后,先取完所有微任务(microtask)队列,再取一个宏任务(macrotask)。微任务包括 Promise.then 的回调、MutationObserver、queueMicrotask;宏任务包括 setTimeout / setInterval、I/O、UI 渲染、postMessage。
记忆口诀是“一宏、清微、渲染、循环”。经典题:setTimeout(fn, 0) 永远晚于 Promise.then,因为微任务在宏任务之前清空。async / await 只是 Promise 的语法糖:await 之后的代码相当于包在 then 回调里,是微任务。
- 单线程 + 事件循环:执行栈空 → 清空所有微任务 → 取一个宏任务
- 微任务:Promise.then / queueMicrotask / MutationObserver
- 宏任务:setTimeout / setInterval / I/O / 渲染;async / await 是 Promise 语法糖
JS
// 经典输出顺序题 console.log('1 脚本开始'); // 同步 setTimeout(() => console.log('2 宏任务 setTimeout'), 0); Promise.resolve().then(() => console.log('3 微任务 promise')); queueMicrotask(() => console.log('4 微任务 queueMicrotask')); console.log('5 脚本结束'); // 输出:1 5 3 4 2 —— 同步代码 → 微任务队列 → 宏任务 // async / await 版 async function async1() { console.log('a1 start'); // 同步部分立即执行 await async2(); // 遇 await 挂起,后续入微任务队列 console.log('a1 end'); // 微任务里执行 } async function async2() { console.log('a2'); } async1(); console.log('script end'); // 输出:a1 start a2 script end a1 end // 用事件循环理解定时器精度:主线程堵塞,定时器也会推迟 const t0 = Date.now(); setTimeout(() => console.log('实际经过约', Date.now() - t0, 'ms'), 100); for (let i = 0; i < 5e7; i++) {} // 堵塞主线程约 100ms 以上
本节要点事件循环 = 执行栈 → 清微任务 → 取宏任务 → 渲染;Promise.then 永远先于 setTimeout。




