#JavaScript 宏任务与微任务详解
一、什么是宏任务和微任务?
1.1 宏任务(Macro Task)
-
宏任务是 JavaScript 中的最外层任务,它包括了浏览器中几乎所有的常见操作,如
- setTimeout 和 setInterval 中的回调函数
- DOM 渲染
- 网络请求的回调(如 AJAX)
- 用户输入(键盘事件、鼠标事件等)
-
每当 JavaScript 引擎执行一个宏任务时,它会按照以下步骤进行
- 从宏任务队列中取出第一个任务。
- 执行这个任务,直到它完成。
- 然后开始下一个宏任务。
1.2 微任务(Micro Task)
-
微任务是在宏任务执行完毕后、下一次事件循环开始前执行的一类任务。微任务通常用于处理一些比宏任务优先级更高的操作,比如:
- Promise 的 .then() 或 .catch() 回调
- MutationObserver(用于监听 DOM 变化)
-
微任务队列的执行顺序优先于宏任务,且在宏任务执行前,如果微任务队列不为空,所有微任务会被执行完毕。
二、事件循环(Event Loop)
JavaScript 是单线程的,这意味着它只能在同一时刻执行一个任务。事件循环机制负责在执行完当前任务后,从任务队列中取出下一个任务并执
- JavaScript 的事件循环有一个明确的执行顺序。事件循环每次迭代的顺序如下:
- 执行栈:首先执行当前正在执行的代码。
- 宏任务队列:执行一个宏任务队列中的任务。
- 微任务队列:执行所有的微任务,直到队列为空。
- 渲染更新:在所有任务完成后,浏览器可能会进行渲染更新。
- 下一轮事件循环。
三、宏任务与微任务执行顺序
console.log('Start');
setTimeout(() => {
console.log('Macro Task 1');
}, 0);
Promise.resolve().then(() => {
console.log('Micro Task 1');
}).then(() => {
console.log('Micro Task 2');
});
setTimeout(() => {
console.log('Macro Task 2');
}, 0);
console.log('End');
// Start
// End
// Micro Task 1
// Micro Task 2
// Macro Task 1
// Macro Task 2
执行过程:
- 首先,console.log(‘Start’) 被执行,输出:Start。
- 接着,setTimeout 被调用,Macro Task 1 会被推入宏任务队列,但它将在当前执行栈清空后才会执行。
- 然后,Promise.resolve().then() 被调用,Micro Task 1 会被推入微任务队列,紧接着 then() 中的第二个回调 Micro Task 2 也被加入微任务队列。
4.setTimeout 再次被调用,Macro Task 2 会被推入宏任务队列。 - console.log(‘End’) 被执行,输出:End。
- 由于当前执行栈已经空了,开始执行微任务:
- Micro Task 1 被执行,输出:Micro Task 1。
- Micro Task 2 被执行,输出:Micro Task 2。
- 最后,宏任务队列中的任务开始执行:
- Macro Task 1 被执行,输出:Macro Task 1。
- Macro Task 2 被执行,输出:Macro Task 2。
四、宏常见问题与误解
5.1 为什么 setTimeout 不会立即执行?
即使你设置了 setTimeout(callback, 0),它也不会在当前执行栈清空后立即执行。它仍然是一个宏任务,意味着它会等待当前执行栈中的代码执行完毕,然后才会进入事件循环执行
5.2 微任务可以"阻塞"宏任务吗?
是的,微任务可以阻塞宏任务。在事件循环过程中,如果微任务队列不为空,微任务会一直被执行,直到队列为空。因此,如果在微任务中加入一个无限循环的微任务,它将阻塞后续的宏任务执行。