​以下内容参考 极客时间 - <<浏览器工作原理与实践>> 专栏

宏任务

前面我们已经介绍过了,页面中的大部分任务都是在主线程上执行的,这些任务包括了:

  1. 渲染事件(如解析 DOM、计算布局、绘制);
  2. 用户交互事件(如鼠标点击、滚动页面、放大缩小等);
  3. JavaScript 脚本执行事件;网络请求完成、文件读写完成事件。

为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列事件循环机制,渲染进程内部会维护一个消息队列。然后主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务。消息队列中的任务是通过事件循环系统来执行的

宏任务的问题

宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合了,比如监听 DOM 变化的需求

微任务

微任务就是一个需要异步执行的函数,是在主函数执行结束之后、当前宏任务结束之前执行

微任务怎么产生的

  1. 使用 MutationObserver 监控某个 DOM 节点,然后再通过 JavaScript 来修改这个节点,或者为这个节点添加、删除部分子节点,当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务。
  2. 使用 Promise,当调用 Promise.resolve() 或者 Promise.reject() 的时候,会产生微任务。当然, 作为语法糖的 async / await 也会产生, 参考 MDN async 函数

微任务何时被执行

在当前宏任务中的 JavaScript 快执行完成时,也就在 JavaScript 引擎准备退出全局执行上下文并清空调用栈的时候,JavaScript 引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务

img

img

总结:

以上就是微任务的工作流程,从上面分析我们可以得出如下几个结论:

  1. 微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。
  2. 微任务的执行时长会影响到当前宏任务的时长。比如一个宏任务在执行过程中,产生了 100 个微任务,执行每个微任务的时间是 10 毫秒,那么执行这 100 个微任务的时间就是 1000 毫秒,也可以说这 100 个微任务让宏任务的执行时间延长了 1000 毫秒。所以你在写代码的时候一定要注意控制微任务的执行时长
  3. 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。

牛刀小试

请说出运行结果并解释流程

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 start')
    await async3()
    console.log('async2 end')
}
async function async3() {
    console.log('async3 start')
}

setTimeout(() => {
    console.log('setTimeout')
},0)

console.log('script start')
async1()
new Promise((resolve) => {
    console.log('promise1')
    setTimeout(resolve, 0)
}).then(() => {
    console.log('promise1 then')
})
new Promise((resolve) => {
    console.log('promise2')
    resolve()
}).then(() => {
    console.log('promise2 then')
})

console.log('script end')

image-20210310224639717

关键逻辑分析

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2 start')
    await async3()
    console.log('async2 end')
}
async function async3() {
    console.log('async3 start')
}

async1()

等价的 Promise 写法为

let async1 = () => {
    console.log('async1 start')
    async2().then(() => {
        console.log('async1 end')
    })
}
let async2 = () => {
    console.log('async2 start')
    return async3().then(() => {
        console.log('async2 end')
    })
}
let async3 = () => {
    console.log('async3 start')
    return Promise.resolve()
}

async1()

再次整理

let async1 = () => {
    console.log('async1 start')
    console.log('async2 start')
    console.log('async3 start')
    return Promise.resolve()  // 产生微任务1, 入队任务为then的的回调
        .then(() => {
          console.log('async2 end')
              // 回调返回值被 resolve(undefined) // 产生微任务3, 入队任务为then的的回调
      })
        .then(() => {
        console.log('async1 end')
    })
}

async1()

new Promise((resolve) => {
    console.log('promise2')
    resolve() // 产生微任务2, 入队任务为then的的回调
}).then(() => {
    console.log('promise2 then')
})


本文由 givencui 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论