宏任务和微任务

144 阅读5分钟

宏任务和微任务

  • 宏任务(Macro Task)和微任务(Micro Task)是用于描述 JavaScript 引擎中不同任务队列的概念,用于控制和管理代码的执行顺序和优先级。

宏任务(Macro Task)

  • 通常包含一组相对较大的任务,例如用户交互事件处理、网络请求、定时器等。宏任务会进入宏任务队列,并按照队列的顺序依次执行。在执行宏任务期间,JavaScript 引擎不会执行其他的任务,因此宏任务的执行是一种较为耗时的操作。

哪些属于宏任务?

  1. script整个代码块是一个大的宏任务。
  2. 用户交互事件:例如点击、滚动、输入等用户交互操作触发的事件处理函数。
  3. 网络请求:通过 XMLHttpRequestfetch 等发送的网络请求操作。
  4. 定时器:使用 setTimeoutsetIntervalrequestAnimationFrame 等设置的定时器任务。
  5. 文件操作:例如读取文件、写入文件等文件系统相关的操作。
  6. 媒体操作:例如音频、视频播放、加载媒体资源等操作。
  7. 延迟调用:例如使用 setTimeout 设置的延迟执行的函数调用。
  8. setImmediate:用于将一个回调函数添加到事件循环中的下一个宏任务队列,以便在当前宏任务执行完毕后立即执行
  • 需要注意的是,不同的 JavaScript 运行环境可能会有不同的宏任务类型,具体的宏任务类型可以根据运行环境的规范或文档来确定。例如,在浏览器中,除了上述提到的常见宏任务,还有一些特定的宏任务类型,如页面渲染、页面导航等。

微任务(Micro Task)

  • 通常包含一组相对较小的任务,例如 Promise 的回调函数、MutationObserver 的回调函数等。微任务会进入微任务队列,并在当前宏任务执行完毕后立即执行。微任务的执行时机在事件循环的各个阶段之间,具有较高的优先级。也就是说,在执行下一个宏任务之前,JavaScript 引擎会首先处理所有的微任务。

哪些属于微任务?

  1. Promise 回调函数:通过 Promise 对象的 then 方法或 catch 方法注册的回调函数。
  2. async/await:使用 async 函数和 await 关键字实现的异步操作。
  3. MutationObserver 回调函数:通过 MutationObserverDOM变化进行监听的回调函数。
  4. Object.observe:已被废弃的 API,用于监听对象的变化。
  5. 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)
      })
  1. 输出结果为:111、222、333、444、555、666
  2. 首先,调用 wait() 函数,开始执行其中的代码。
  3. wait() 函数内部,创建了一个 Promise 对象并返回。
  4. 进入 Promise 的执行器函数,执行其中的代码。
  5. 遇到 setTimeout,将其添加到宏任务队列中,等待下一轮事件循环执行。
  6. 打印输出 111
  7. 调用 resolve(222),将 Promise 状态设为已解析(fulfilled),并将结果值设为 222
  8. 返回到 wait().then() 中的 Promise 链,继续执行。
  9. 打印输出 222
  10. 打印输出 333
  11. 执行微任务队列中的微任务,即上一步中创建的内部 Promise 的回调函数。
  12. 打印输出 444
  13. 进入内部 Promise 的执行器函数,执行其中的代码。
  14. 打印输出 555
  15. 调用内部 Promise 的 resolve(666),将其状态设为已解析(fulfilled),并将结果值设为 666
  16. 返回到内部 Promise 的 .then() 中的回调函数,继续执行。
  17. 打印输出 666