宏任务与微任务

159 阅读3分钟

随着浏览器的应用领域越来越广泛,消息队列中这种粗时间颗粒度的任务已经不能胜任部分领域的需求,所以又出现了一种新的技术——微任务。微任务可以在实时性和效率之间做一个有效的权衡。

宏任务

页面中大部分任务都是在主线程上执行的,这些任务包括:

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

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

页面的渲染事件、各种 IO 的完成事件、执行 JavaScript 脚本的事件、用户交互的事件等都随时有可能被添加到消息队列中,而且添加事件是由系统操作的,JavaScript 代码不能准确掌控任务要添加到队列中的位置,控制不了任务在消息队列中的位置,所以很难控制开始执行任务的时间。 所以说宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合了。

微任务

异步回调主要有两种方式:

  • 将异步回调函数封装成一个宏任务,添加到消息队列尾部,当循环系统执行到该任务的时候执行回调函数, setTimeout就是这种。
  • 主函数执行结束之后、当前宏任务结束之前执行回调函数,这通常都是以微任务形式体现的。

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

现在浏览器产生微任务有两种方式 :

  • 使用 MutationObserver 监控某个 DOM 节点,然后再通过 JavaScript 来修改这个节点,或者为这个节点添加、删除部分子节点,当 DOM 节点发生变化时,就会产生 DOM 变化记录的微任务
  • 使用 Promise,当调用 Promise.resolve() 或者 Promise.reject() 的时候,也会产生微任务。 即promise.then() catch finally都是微任务。
setTimeout(_ => {
  console.log(4)
})

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)

这里就是按顺序执行的。首先遇到setTimeout的回调,这里是个宏任务,放入异步队列等待执行;执行到new promise时,我们知道promsie里的执行函数都是立即执行的,这里就开始打印1,随后到then,这里有一个微任务,也先放入任务队列中,随后打印2;微任务是先于宏任务执行的,就先打印3,后打印4。