JS 是单线程的(同一时刻只做一件事),但浏览器是多线程的:DOM 定时器、网络请求交给宿主线程跑,完成后再把回调塞回 JS 的任务队列。事件循环就是「调用栈 → 微任务队列 → 一个宏任务 → 微任务队列 → …」的循环。背熟这句话:每执行完一个宏任务(包括 script 整体),要把微任务清空,再取下一个宏任务。
微任务(microtask)包括 Promise.then、queueMicrotask、MutationObserver;宏任务(macrotask)包括 setTimeout、setInterval、I/O、UI 渲染。这就是「Promise 比 setTimeout 快」的全部原因——不是 Promise 更快,而是它排在更前面的队列。
console.log(1); setTimeout(() => console.log(2), 0); // 宏任务,最后执行 Promise.resolve().then(() => console.log(3)); // 微任务,先于宏任务 console.log(4); // 输出: 1 4 3 2 —— 永远是 微任务 先于 宏任务 // 实战:用事件循环理解为什么 await 后面的代码「延迟」执行 async function f() { console.log('a'); await null; console.log('b'); } f(); console.log('c'); // a c b(b 被推到微任务队列)