JavaScript是单线程语言,所有任务都需要按顺序执行,为了处理异步操作,引入了事件循环和任务队列的概念,宏任务队列就是其中一类重要的任务队列,它的执行顺序遵循特定的规则。

宏任务的基本概念
宏任务指的是由宿主环境(如浏览器、Node.js)发起的异步任务,常见的宏任务类型包括setTimeout、setInterval、setImmediate(Node.js环境)、requestAnimationFrame(浏览器环境)、I/O操作、UI渲染等。
每个宏任务执行完成后,会检查是否存在微任务,所有微任务执行完毕之后,才会执行下一个宏任务,这是事件循环的核心逻辑之一。
宏任务队列的执行顺序规则
宏任务队列的执行顺序遵循以下核心规则:
- 首先执行当前主线程的同步代码,同步代码属于第一个宏任务的一部分。
- 同步代码执行完成后,从宏任务队列中取出最早进入队列的宏任务执行。
- 单个宏任务执行完成后,会立即执行当前所有待执行的微任务,直到微任务队列清空。
- 微任务执行完毕后,再取出下一个最早进入宏任务队列的任务执行,重复上述流程。
代码示例演示执行顺序
下面通过一个浏览器环境的代码示例,直观展示宏任务队列的执行顺序:
// 同步代码,属于第一个宏任务
console.log('同步代码1');
// setTimeout是宏任务,延迟0毫秒,会被加入宏任务队列
setTimeout(() => {
console.log('setTimeout1回调');
// 回调内部的Promise.then是微任务
Promise.resolve().then(() => {
console.log('setTimeout1内的微任务');
});
}, 0);
// 另一个setTimeout宏任务
setTimeout(() => {
console.log('setTimeout2回调');
}, 0);
// 同步代码
console.log('同步代码2');
// 主线程同步代码执行完成后,执行微任务队列中的任务
Promise.resolve().then(() => {
console.log('同步后的微任务1');
});
Promise.resolve().then(() => {
console.log('同步后的微任务2');
});
上述代码的执行结果依次为:
- 同步代码1
- 同步代码2
- 同步后的微任务1
- 同步后的微任务2
- setTimeout1回调
- setTimeout1内的微任务
- setTimeout2回调
可以看到,两个setTimeout的回调按照进入宏任务队列的顺序依次执行,每个宏任务执行后都会先清空微任务队列,再执行下一个宏任务。
不同宏任务类型的入队顺序影响
如果不同类型的宏任务同时被触发,进入队列的顺序会影响执行顺序,比如浏览器中requestAnimationFrame和setTimeout的执行顺序:
// requestAnimationFrame是浏览器宏任务,在下一帧渲染前执行
requestAnimationFrame(() => {
console.log('requestAnimationFrame回调');
});
// setTimeout延迟0毫秒
setTimeout(() => {
console.log('setTimeout回调');
}, 0);
在浏览器环境中,requestAnimationFrame的回调会在setTimeout回调之前执行,因为requestAnimationFrame的触发时机早于setTimeout的入队时机,所以会先进入宏任务队列,先被执行。
常见误区说明
很多开发者会误以为setTimeout的延迟时间到了就会立即执行,实际上延迟时间只是任务被加入宏任务队列的最早时间,如果此时主线程或者前面的宏任务、微任务还在执行,该setTimeout回调就需要等待前面的任务执行完成才能运行。
比如在下面的例子中,setTimeout的延迟是0毫秒,但因为主线程有耗时操作,回调会被推迟执行:
console.log('开始');
setTimeout(() => {
console.log('setTimeout回调');
}, 0);
// 模拟耗时100毫秒的同步操作
const start = Date.now();
while (Date.now() - start < 100) {}
console.log('结束');
执行结果是先输出开始、结束,再输出setTimeout回调,因为耗时同步操作占用了主线程,导致setTimeout回调只能等同步操作完成后才能进入执行流程。
总结
JavaScript中宏任务队列的执行顺序核心是先进先出,同时遵循事件循环的规则:单个宏任务执行完成后必须清空所有微任务,再执行下一个宏任务。不同类型的宏任务入队时机不同,会影响最终的执行顺序,开发者需要结合具体的宏任务类型和入队时间判断执行顺序,避免异步代码的逻辑错误。
JavaScript宏任务队列事件循环setTimeout任务调度修改时间:2026-07-12 15:21:26