JavaScript的宏任务与微任务

在介绍前端宏任务与微任务以前,先列出来一道题,一块看一下。html

console.log('1')
setTimeout(() => {
  console.log('2')
})
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(() => {
  console.log('4')
})
console.log(5)复制代码

诸位能够先给出来一个本身的答案,运行一下结果,看看是否与本身想的一致。前端

1.基本概念

这里介绍一下JavaScript里面的一些基本知识web

  1. 关于代码执行环境,JavaScript代码执行时,引擎会创造出来当前代码块的执行环境,在涉及到使用变量时,只能查找到当前环境的变量和包含当前执行环境的外部环境变量。全局环境是最外层的执行环境
  2. JavaScript是单线程
  3. JavaScript在处理异步操做时,利用的是事件循环机制。

2.宏任务、微任务与事件循环机制

了解事件循环的同窗都知道,在事件循环中,异步事件并不会放到当前任务执行队列,而是会被挂起,放入另一个回调队列。当前的任务队列执行结束之后,JavaScript引擎回去检查回调队列中是否有等待执行的任务,如有会把第一个任务加入执行队列,而后不断的重复这个过程。api

从现象上来看,宏任务和微任务产生的异步操做,都会在执行队列完成后再执行,因此貌似宏任务和微任务都放到回调队列中。promise

真的是这样吗?浏览器

确定不是。若是真的是这样,那宏任务和微任务在乎义上便没有区别了。bash

3.宏任务与微任务

首先咱们确定要坚持一点:宏任务和微任务在乎义上确定是有绝对区别的。app

看一下在浏览器环境下可以触发宏任务的操做都有哪些(其余环境下会有不一样):webapp

  1. I/O 操做
  2. setTimeout
  3. setInterval
  4. requestAnimationFrame(争议,后面会讨论)

以 setTimeout 为例。因为 JavaScript 是单线程,因此 setTimeout 的计时操做必定不是JavaScript来作的,不然会形成代码执行的阻塞。异步

那么这种操做是由谁来作的?是宿主环境。以浏览器为例子,JavaScript 在执行到 setTimeout 时会告诉浏览器:“Hey boy!这有个定时器,你帮我看着点,等到点了你告诉我一下”。这时候浏览器就会进行一个计时操做,计时完成之后,将 setTimeout 的回调放入 JavaScript 事件循环的回调队列中。这样 JavaScript 就能够在接下来的执行中处理这个回调。

咱们看一下上面列出来的4点触发宏任务的操做,所有与浏览器相关!

因此,我我的的理解是:宏任务即是 JavaScript 与宿主环境产生的回调,须要宿主环境配合处理而且会被放入回调队列的任务都是宏任务。

浏览器下触发微任务的操做为:

  1. Promise
  2. MutationObserver

这两个操做也都可以产生异步操做,那为何与宏任务不同呢。这里就要涉及到事件循环的另外一个队列了--做业队列(微任务队列)。

为了更好的理解做业队列,咱们把执行队列从开始到结束这样的一个过程,称为一个tick,回调队列的第一个事件则会在下一个tick中被执行,第二个事件会在下下个tick中...这样依次执行。

而做业队列则是位于当前tick的最尾部,在当前tick中添加的微任务都不会留到下一个tick,而是在tick的尾部触发执行。

一个事件循环中,在执行队列里的任务执行完毕之后,会有一个单独的步骤,叫 Perform a microtask checkpoint,即执行微任务检查点。这个操做是检查做业队列中是否有微任务,若是有,便将做业队也会看成执行队列来继续执行,完毕后将执行队列置空。

因此,这里咱们就能够肯定的说:同一个执行队列产生的微任务老是会在宏任务以前被执行

那么,咱们如今回答第三点开始提出来的问题,宏任务和微任务的意义区别在哪呢?

我的的理解是宏任务是可以在宿主环境的协助下,经过回调队列来完成异步操做,微任务则是在宏任务执行前,进行某些操做,告诉 Javascript 引擎在处理完当前执行队列后,尽快地执行咱们的代码。

4.关于requestAnimationFrame

起初我对requestAnimationFrame的定义是宏任务,由于在测试requestAnimationFrame的时候我用了下面这段代码

const testElement = document.getElementById('testElement')
setTimeout(() => {
  console.log(performance.now(), 'settimeout')
}, 0)
requestAnimationFrame(() => {
  console.log(performance.now(),
 'requestAnimationFrame')
})
var observer = new MutationObserver(() => {
  console.log('MutationObserver')
});
observer.observe(testElement, {
 childList: true 
})
const div = document.createElement('div')testElement.appendChild(div)
new Promise(resolve => {
  console.log('promise')  resolve()
}).then(() => console.log('then'))
console.log(performance.now(), 'global')复制代码

在浏览器的输出会有差别,屡次运行之后出现了两种结果

第一种是:


另一个是:


起初我将requestAnimationFrame归到宏任务中,缘由是它绝大多数都会在setTimeout回调执行以后才执行。并将这个结果解释为是因为浏览器在执行渲染的时候,每次执行的时间会有差别,因此致使requestAnimationFrame和setTimeout被压入回调回咧的时机不一致,也就致使了回调的时间不一致。

但这种强行解释仍是站不住脚,嘿嘿,我等做为一名立志成为优秀 Programer 的有志青年,确定仍是须要找找论据。      ----____是南风

后来在查了一些资料,在看了这篇规范文档后,发现在一个事件循环的tick中是包含浏览器渲染过程的,requestAnimationFrame的触发是在浏览器重绘以前,MDN文档介绍以下:

window.requestAnimationFrame() 告诉浏览器——你但愿执行一个动画,而且要求浏览器在下次重绘以前调用指定的回调函数更新动画。该方法须要传入一个回调函数做为参数,该回调函数会在浏览器下一次重绘以前执行

因此,requestAnimationFrame的回调时机也是在当前的tick中,因此不属于宏任务,但也不是微任务,排在微任务以后。


固然,这个问题若是有大佬能够赐教,欢迎评论区留言。/手动撒花 /手动撒花

5.总结

微任务与宏任务是我在处理一个七星瓢虫的时候,偶然接触到的知识。整理完这份文章,感受对 JavaScript 事件循环的理解又深刻了一点。也但愿对阅读到这篇文档的你能产生帮助,哈哈。

“任何能够用JavaScript来写的应用,最终都将用JavaScript来写” --- 阿特伍德定律

附补充

  1. Node环境下宏任务:
    1. I/O 操做
    2. setTimeout
    3. setInterval
    4. setImmediate
  2. Node环境下微任务:
    1. Promise
    2. process.nextTick
  3. 增长了while循环进行延时,你能对事件循环感觉的更清楚:
console.log('1')
setTimeout(() => {
  console.log('2')
})
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(() => {
  let i = 0
  while(i < 1000000000) {
    i++
  }
  console.log('4')
})
let i = 0
while(i < 1000000000) {
  i++
}
console.log(5)复制代码
相关文章
相关标签/搜索