面试题库 词条目录
前端Wiki/面试题库/JavaScript/JS 进阶题

事件循环与宏任务微任务

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。