理解 JavaScript 单线程模型与事件循环的关系
掌握调用栈、任务队列(宏任务)与微任务队列的执行顺序
能够解释常见异步代码的输出顺序
识别并避免阻塞主线程的常见陷阱
应用最佳实践编写高效、可维护的异步代码
JavaScript 是单线程语言,意味着它一次只能执行一个任务。然而,现代 Web 应用需要处理网络请求、用户交互、定时器等异步操作,而不能让这些操作阻塞主线程。
事件循环(Event Loop) 正是解决这一矛盾的核心机制。它协调同步代码与异步回调的执行,确保程序在不阻塞的情况下高效运行。

console.log("Start");
setTimeout(() => {
console.log("定时器回调");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 已解析");
});
console.log("End");输出结果:
Start
End
Promise 已解析
定时器回调执行流程:
console.log("Start") 同步执行 → 输出 "Start"
setTimeout 被交给 Web API,其回调进入任务队列(宏任务)
Promise.resolve().then() 的回调被放入微任务队列
console.log("End") 同步执行 → 输出 "End"
同步代码执行完毕,调用栈清空
事件循环先处理微任务队列 → 输出 "Promise 已解析"
再处理任务队列 → 输出 "定时器回调"
关键规则:微任务优先级高于宏任务,每次宏任务执行前后都会清空微任务队列。
while (true) {
console.log('阻塞中...');
}无限循环会持续占用调用栈,导致事件循环无法处理任何异步任务。
结果:页面卡死、无响应。
解决方案:将耗时计算拆分为多个小任务,使用 setTimeout 或 queueMicrotask 分片执行,或使用 Web Workers。
setTimeout 不准时console.log("开始");
setTimeout(() => console.log("定时器触发"), 1000);
for (let i = 0; i < 1e9; i++) {} // 耗时循环
console.log("结束");尽管设置了 1000ms 延迟,但只有当调用栈空闲时,setTimeout 回调才能执行。
如果主线程被长时间占用,回调会被延迟执行。
提示:
setTimeout(fn, 0)并非“立即执行”,而是“尽快在下一个宏任务中执行”。
setTimeout(() => console.log("宏任务"), 0);
Promise.resolve().then(() => console.log("微任务"));
console.log("同步代码");输出:
同步代码
微任务
宏任务微任务总是在当前宏任务结束后、下一个宏任务开始前执行。
即使 setTimeout 设为 0ms,也无法超越微任务。
setTimeout(() => {
console.log("步骤 1");
setTimeout(() => {
console.log("步骤 2");
setTimeout(() => {
console.log("步骤 3");
}, 1000);
}, 1000);
}, 1000);多层嵌套导致代码难以阅读、调试和维护。
解决方案:
使用 Promise 链式调用
使用 async/await 语法糖
async function runSteps() {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("步骤 1");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("步骤 2");
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("步骤 3");
}小技巧:在 Node.js 中,若需在 I/O 阶段后立即执行高优先级任务,可使用
setImmediate(),它比setTimeout(fn, 0)更快进入队列。
JavaScript 是单线程,靠事件循环实现异步非阻塞。
执行顺序:同步代码 → 微任务队列 → 宏任务队列。
微任务(如 Promise.then)优先级高于宏任务(如 setTimeout)。
阻塞调用栈会导致整个应用卡死,务必避免。
使用 async/await 和 Promise 替代回调嵌套,提升代码可读性。
利用开发者工具监控事件循环性能,及时发现瓶颈。
为什么 Promise.resolve().then() 总是在 setTimeout(..., 0) 之前执行?这背后的队列机制是什么?
如果在一个 Promise.then() 中又添加了新的 Promise.then(),这些微任务会如何执行?它们会阻塞下一个宏任务吗?
在浏览器中,requestAnimationFrame 属于宏任务还是微任务?它与事件循环的关系是怎样的?