在JavaScript的事件循环(Event Loop)中,任务被分为宏任务(MacroTask)和微任务(MicroTask)两种。这两种任务类型在事件循环中有不同的处理优先级和执行时机。
宏任务(MacroTask)
宏任务包括:script(整个代码脚本)、setTimeout、setInterval、setImmediate(Node.js环境)、I/O、UI渲染等。每个宏任务都会触发一个新的事件循环。
宏任务的特点是:每个宏任务都会开始一个新的任务队列,并且只有当前宏任务队列中的所有任务都执行完成后,才会检查微任务队列并执行其中的任务。
微任务(MicroTask)
微任务包括:Promise的then和catch、MutationObserver(浏览器环境)、process.nextTick(Node.js环境)等。微任务会在每个宏任务之后立即执行,即在一个宏任务执行完后,会立即执行所有微任务,然后再开始下一个宏任务。
微任务的特点是:在当前宏任务执行完后,会立即执行所有微任务,无需等待下一个宏任务。
结合例子谈谈
下面是一个结合了宏任务和微任务的例子:
console.log('1. 开始'); // 宏任务
setTimeout(function() {
console.log('2. setTimeout回调'); // 宏任务
Promise.resolve().then(function() {
console.log('4. Promise在setTimeout中回调'); // 微任务
});
}, 0);
process.nextTick(function() {
console.log('3. process.nextTick回调'); // 微任务
});
new Promise(function(resolve) {
console.log('5. Promise创建'); // 微任务
resolve();
}).then(function() {
console.log('6. Promise在全局作用域中回调'); // 微任务
});
setTimeout(function() {
console.log('7. 第二个setTimeout回调'); // 宏任务
process.nextTick(function() {
console.log('8. process.nextTick在第二个setTimeout中回调'); // 微任务
});
}, 0);
console.log('9. 结束'); // 宏任务
在这个例子中,我们有多个宏任务和微任务交织在一起。现在,我们来分析它们的执行顺序:
- 执行
console.log('1. 开始')
,这是第一个宏任务。 - 接着,我们遇到
setTimeout
,它会在当前宏任务结束后被放入宏任务队列。 - 然后,我们有一个
process.nextTick
,它是一个微任务,会在当前宏任务结束后、下一个宏任务开始前立即执行。因此,它会打印3. process.nextTick回调
。 - 接下来,我们创建了一个新的
Promise
,并在其创建时执行了console.log('5. Promise创建')
,这是微任务。 - 当
Promise
被解析时,它的then
方法中的回调函数会被放入微任务队列。但是,由于前面已经有了一个process.nextTick
微任务,因此这个Promise
的回调会等待process.nextTick
执行完毕后再执行。 - 在执行完所有的微任务后(即
process.nextTick
和Promise
的回调),我们回到宏任务队列,并执行setTimeout
的回调。此时,会打印2. setTimeout回调
。 - 在
setTimeout
的回调中,我们又创建了一个新的Promise
,并在其解析后执行了console.log('4. Promise在setTimeout中回调')
。这是一个微任务,但由于它是在setTimeout
的回调中创建的,因此会等待setTimeout
的宏任务执行完毕后再执行。 - 当
setTimeout
的宏任务和其中的微任务都执行完毕后,我们再次回到宏任务队列,并执行第二个setTimeout
的回调。此时,会打印7. 第二个setTimeout回调
。 - 在第二个
setTimeout
的回调中,我们又有一个process.nextTick
微任务,它会在当前宏任务结束后、下一个宏任务开始前立即执行。因此,它会打印8. process.nextTick在第二个setTimeout中回调
。 - 最后,执行
console.log('9. 结束')
,这是最后一个宏任务。
因此,上述代码的输出结果将是:
1. 开始
9. 结束
3. process.nextTick回调
5. Promise创建
6. Promise在全局作用域中回调
2. setTimeout回调
4. Promise在setTimeout中回调
7. 第二个setTimeout回调
8. process.nextTick在第二个setTimeout中回调
这个例子清楚地展示了宏任务和微任务在JavaScript事件循环中的执行顺序。宏任务之间互不影响,每个宏任务都会开始一个新的任务队列,并在其结束后检查并执行微任务队列中的所有任务。微任务则会在当前宏任务结束后、下一个宏任务开始前立即执行。这种机制允许我们更精细地控制代码的执行顺序,尤其是在需要处理异步操作时。