#25
事件循环(event loop)
Champ2026.01.03 23:04created at 2026.01.03 23:04updated at 2026.01.13 17:55
0 次阅读

事件循环是什么?
事件循环是 JavaScript 实现异步编程的核心机制,它解决了单线程语言如何处理并发操作的问题。
一、为什么需要事件循环?
JavaScript 是单线程语言,意味着同一时间只能执行一个任务。如果没有事件循环:
- 遇到网络请求、文件读取等耗时操作时,整个线程会被阻塞
- 页面会卡死,无法响应用户交互
事件循环通过异步回调机制,让 JavaScript 可以在等待 I/O 操作时继续执行其他代码。
二、核心组成部分
┌───────────────────────────┐
│ Call Stack │ ← 当前正在执行的函数(执行上下文栈)
└─────────────┬─────────────┘
│
│ 执行完毕
↓
┌───────────────────────────┐
│ Web APIs / Node APIs │ ← 浏览器/Node提供的异步API(定时器、网络请求等)
└─────────────┬─────────────┘
│
│ 异步任务完成,将回调放入队列
↓
┌───────────────────────────┐
│ Task Queue (宏任务队列) │ ← 宏任务回调(setTimeout、setInterval、I/O等)
│Microtask Queue (微任务队列)│ ← 微任务回调(Promise.then、process.nextTick等)
└─────────────┬─────────────┘
│
│ 事件循环从队列中取出任务
↓
┌───────────────────────────┐
│ Event Loop │ ← 持续检查队列,将任务推入调用栈执行
└───────────────────────────┘
三、执行顺序规则
- 同步代码优先执行,遇到异步任务交给 Web APIs 处理
- 微任务优先级高于宏任务
- 同一层级的微任务会全部执行完,才会执行下一个宏任务
- 每个宏任务执行后都会清空微任务队列
// 经典面试题
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
}, 0);
Promise.resolve().then(() => {
console.log('4'); // 微任务
setTimeout(() => {
console.log('5'); // 宏任务
}, 0);
});
console.log('6'); // 同步
// 输出顺序:1, 6, 4, 2, 3, 5
/**第一步:执行同步代码
执行 console.log('1'),输出 1
遇到 setTimeout,将其回调函数放入 宏任务队列(等待执行)
遇到 Promise.resolve().then,将其回调函数放入 微任务队列
执行 console.log('6'),输出 6
此时输出:1, 6
第二步:同步代码执行完毕,检查微任务队列
微任务队列中有第一个Promise的回调,立即执行:
执行 console.log('4'),输出 4
遇到内部的 setTimeout,将其回调放入 宏任务队列
此时输出:1, 6, 4
第三步:微任务队列清空,执行下一个宏任务
从宏任务队列中取出第一个 setTimeout 的回调执行:
执行 console.log('2'),输出 2
遇到 Promise.resolve().then,将其回调放入 微任务队列
此时输出:1, 6, 4, 2
第四步:宏任务中的微任务立即执行
当前宏任务执行完毕,检查微任务队列:
执行 console.log('3'),输出 3
此时输出:1, 6, 4, 2, 3
第五步:执行下一个宏任务
从宏任务队列中取出第二个 setTimeout 的回调执行:
执行 console.log('5'),输出 5
最终输出:1, 6, 4, 2, 3, 5
**/
四、任务分类详细
宏任务(Macrotasks/Tasks)
setTimeout、setIntervalsetImmediate(Node.js)requestAnimationFrame(浏览器)- I/O 操作(文件读取、网络请求)
- UI 渲染(浏览器)
- 整体 script 代码
微任务(Microtasks)
Promise.then()、Promise.catch()、Promise.finally()async/await(本质是 Promise)MutationObserver(浏览器)process.nextTick(Node.js,优先级最高)queueMicrotask()
//示例
setTimeout(() => {
console.log('Timeout callback');
}, 0);
Promise.resolve().then(() => {
console.log('Promise callback');
});
console.log('Main thread execution');
//执行结果
Main thread execution
Promise callback
Timeout callback
五、async/await 的特殊处理
重点:await后面的代码相当于then回调(属于微任务)!
async function async1() {
console.log('async1 start'); // 同步
await async2(); // await后面的代码相当于then回调(微任务)
console.log('async1 end'); // 微任务
}
async function async2() {
console.log('async2'); // 同步
}
console.log('script start'); // 同步
setTimeout(() => {
console.log('setTimeout'); // 宏任务
}, 0);
async1();
new Promise(resolve => {
console.log('promise1'); // 同步
resolve();
}).then(() => {
console.log('promise2'); // 微任务
});
console.log('script end'); // 同步
//宏:settimeout,
//微:async1 end,promise2,
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
七、面试常见问题与解答
Q1: setTimeout(fn, 0) 真的是0毫秒后执行吗?
不是。0毫秒表示尽快执行,但实际至少要等:
- 当前执行栈清空
- 微任务队列清空
- 浏览器可能有最小延迟(通常4ms)
Q2: 如何实现一个0延迟的定时器?
// 使用 MessageChannel 或 postMessage
function zeroTimeout(fn) {
const channel = new MessageChannel();
channel.port1.onmessage = fn;
channel.port2.postMessage(null);
}
Q3: 什么是"任务饥饿"?
如果微任务中不断产生新的微任务,会导致宏任务永远无法执行。