JavaScript 主线程与异步队列执行顺序详解
JavaScript 是单线程语言,通过事件循环(Event Loop)机制来处理同步和异步任务。以下是主线程与异步队列的执行顺序解析:
1. 执行顺序基本原则
console.log('1. 主线程同步任务');
setTimeout(() => {
console.log('6. 宏任务队列 - setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('4. 微任务队列 - Promise');
});
console.log('2. 主线程同步任务');
queueMicrotask(() => {
console.log('5. 微任务队列 - queueMicrotask');
});
console.log('3. 主线程同步任务');
// 输出顺序:
// 1. 主线程同步任务
// 2. 主线程同步任务
// 3. 主线程同步任务
// 4. 微任务队列 - Promise
// 5. 微任务队列 - queueMicrotask
// 6. 宏任务队列 - setTimeout
2. 执行顺序详细机制
执行栈(Call Stack)
- 同步代码立即执行
- 函数调用形成栈帧
任务队列(Task Queue)
微任务队列(Microtask Queue):
- Promise 回调(then/catch/finally)
- queueMicrotask()
- MutationObserver(浏览器)
- process.nextTick(Node.js)
宏任务队列(Macrotask Queue):
- setTimeout/setInterval
- I/O 操作
- UI 渲染(浏览器)
- setImmediate(Node.js)
- requestAnimationFrame(浏览器)
3. 完整事件循环流程
- 执行当前同步代码(主线程)
- 执行完所有同步代码后,检查微任务队列
- 执行所有微任务(直到队列为空)
- 如有需要,进行UI渲染(浏览器环境)
- 从宏任务队列取出一个任务执行
- 重复上述过程(事件循环)
4. 复杂示例分析
console.log('脚本开始');
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('Promise in setTimeout1');
});
}, 0);
setTimeout(() => {
console.log('setTimeout2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise1');
Promise.resolve().then(() => {
console.log('Promise in Promise1');
});
});
Promise.resolve().then(() => {
console.log('Promise2');
});
console.log('脚本结束');
/* 输出顺序:
脚本开始
脚本结束
Promise1
Promise2
Promise in Promise1
setTimeout1
Promise in setTimeout1
setTimeout2
*/
5. Node.js 与浏览器的差异
环境 | 微任务优先级 | 宏任务优先级顺序 |
---|---|---|
浏览器 | Promise > MutationObserver | 动画帧 > I/O > 定时器 > UI渲染 |
Node.js | process.nextTick > Promise | setImmediate > setTimeout/setInterval |
6. 实践建议
长时间运行的微任务会阻塞渲染和宏任务执行
// 避免这种情况 Promise.resolve().then(() => { while(true) { /* 无限循环 */ } });
合理分配任务类型:
- 高优先级任务:使用微任务
- 低优先级任务:使用宏任务
避免嵌套太深:
// 不易维护的深层嵌套 setTimeout(() => { Promise.resolve().then(() => { setTimeout(() => { // ... }, 0); }); }, 0);
理解这些执行顺序规则对于调试异步代码、优化性能和避免竞态条件至关重要。