宏任务和微任务
- 宏任务(Macro Task)和微任务(Micro Task)是用于描述 JavaScript 引擎中不同任务队列的概念,用于控制和管理代码的执行顺序和优先级。
宏任务(Macro Task)
- 通常包含一组相对较大的任务,例如用户交互事件处理、网络请求、定时器等。宏任务会进入宏任务队列,并按照队列的顺序依次执行。在执行宏任务期间,JavaScript 引擎不会执行其他的任务,因此宏任务的执行是一种较为耗时的操作。
哪些属于宏任务?
script整个代码块是一个大的宏任务。
- 用户交互事件:例如
点击、滚动、输入等用户交互操作触发的事件处理函数。
- 网络请求:通过
XMLHttpRequest、fetch 等发送的网络请求操作。
- 定时器:使用
setTimeout、setInterval、requestAnimationFrame 等设置的定时器任务。
- 文件操作:例如读取文件、写入文件等文件系统相关的操作。
- 媒体操作:例如音频、视频播放、加载媒体资源等操作。
- 延迟调用:例如使用 setTimeout 设置的延迟执行的函数调用。
setImmediate:用于将一个回调函数添加到事件循环中的下一个宏任务队列,以便在当前宏任务执行完毕后立即执行
- 需要注意的是,不同的 JavaScript 运行环境可能会有不同的宏任务类型,具体的宏任务类型可以根据运行环境的规范或文档来确定。例如,在浏览器中,除了上述提到的常见宏任务,还有一些特定的宏任务类型,如页面渲染、页面导航等。
微任务(Micro Task)
- 通常包含一组相对较小的任务,例如 Promise 的回调函数、MutationObserver 的回调函数等。微任务会进入微任务队列,并在当前宏任务执行完毕后立即执行。微任务的执行时机在事件循环的各个阶段之间,具有较高的优先级。也就是说,在执行下一个宏任务之前,JavaScript 引擎会首先处理所有的微任务。
哪些属于微任务?
Promise 回调函数:通过 Promise 对象的 then 方法或 catch 方法注册的回调函数。
async/await:使用 async 函数和 await 关键字实现的异步操作。
MutationObserver 回调函数:通过 MutationObserver 对DOM变化进行监听的回调函数。
Object.observe:已被废弃的 API,用于监听对象的变化。
queueMicrotask:用于添加一个微任务到微任务队列中的方法。
- 微任务会被添加到微任务队列中,并在当前宏任务执行完毕后立即执行。微任务具有较高的优先级,意味着它们会在下一个宏任务之前执行,确保它们在其他任务之前被处理。
- 需要注意的是,微任务的执行顺序是先进先出(FIFO),即按照它们被添加到微任务队列的顺序依次执行。
事件循环(Event Loop)
- 事件循环(Event Loop)是 JavaScript 引擎用于协调和管理宏任务和微任务执行的机制。在事件循环中,JavaScript 引擎会不断地从宏任务队列中取出一个宏任务进行执行,然后检查并执行微任务队列中的所有微任务,直到微任务队列为空。然后,JavaScript 引擎会再次从宏任务队列中取出下一个宏任务进行执行,以此类推。
下面是一个简化的事件循环的示意图:
┌─────────────────┐
│ 宏任务队列 │
├─────────────────┤
│ 宏任务 1 │
│ 宏任务 2 │
│ ... │
└─────────────────┘
│
▼
┌─────────────────┐
│ 微任务队列 │
├─────────────────┤
│ 微任务 1 │
│ 微任务 2 │
│ ... │
└─────────────────┘
│
▼
┌─────────────────┐
│ 执行微任务 │
└─────────────────┘
│
▼
┌─────────────────┐
│ 执行下一个 │
│ 宏任务 │
└─────────────────┘
示例1:
console.log(0);
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
console.log(6)
}).then(function(val){
console.log(val);
})
console.log(4)
- 输出结果为 0 、 2 、6 、 4 、 3 、 1
- 0 、 2 、6 、 4 、 3 到这里本轮事件循环结束 ; 然后下一个宏任务开启 输出 1
示例2:
function wait(){
return new Promise((resolve) => {
setTimeout(() => {
console.log(444)
const p = new Promise((resolve) => {
console.log(555)
resolve(666)
}).then(res => {
console.log(res)
})
})
console.log(111)
resolve(222)
})
}
wait().then(res => {
console.log(res)
console.log(333)
})
- 输出结果为:111、222、333、444、555、666
- 首先,调用
wait() 函数,开始执行其中的代码。
- 在
wait() 函数内部,创建了一个 Promise 对象并返回。
- 进入 Promise 的执行器函数,执行其中的代码。
- 遇到
setTimeout,将其添加到宏任务队列中,等待下一轮事件循环执行。
- 打印输出
111。
- 调用
resolve(222),将 Promise 状态设为已解析(fulfilled),并将结果值设为 222。
- 返回到
wait().then() 中的 Promise 链,继续执行。
- 打印输出
222。
- 打印输出
333。
- 执行微任务队列中的微任务,即上一步中创建的内部 Promise 的回调函数。
- 打印输出
444。
- 进入内部 Promise 的执行器函数,执行其中的代码。
- 打印输出
555。
- 调用内部 Promise 的
resolve(666),将其状态设为已解析(fulfilled),并将结果值设为 666。
- 返回到内部 Promise 的
.then() 中的回调函数,继续执行。
- 打印输出
666。