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('脚本结束');/* 输出顺序:脚本开始脚本结束Promise1Promise2Promise in Promise1setTimeout1Promise in setTimeout1setTimeout2
*/
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);
理解这些执行顺序规则对于调试异步代码、优化性能和避免竞态条件至关重要。