黑马程序员技术交流社区

标题: 【上海校区】JavaScript的宏任务与微任务 [打印本页]

作者: LittlePrince    时间: 2019-4-11 13:52
标题: 【上海校区】JavaScript的宏任务与微任务
在介绍前端宏任务与微任务之前,先列出来一道题,一块看一下。




[JavaScript] 纯文本查看 复制代码
console.log('1')
setTimeout(() => {
  console.log('2')
})
new Promise((resolve, rejects) => {
  console.log('3')
  resolve()
}).then(() => {
  console.log('4')
})
console.log(5)

诸位可以先给出来一个自己的答案,运行一下结果,看看是否与自己想的一致。
1.基本概念这里介绍一下JavaScript里面的一些基本知识
2.宏任务、微任务与事件循环机制了解事件循环的同学都知道,在事件循环中,异步事件并不会放到当前任务执行队列,而是会被挂起,放入另外一个回调队列。当前的任务队列执行结束以后,JavaScript引擎回去检查回调队列中是否有等待执行的任务,若有会把第一个任务加入执行队列,然后不断的重复这个过程。
从现象上来看,宏任务和微任务产生的异步操作,都会在执行队列完成后再执行,所以貌似宏任务和微任务都放到回调队列中。
真的是这样吗?
肯定不是。如果真的是这样,那宏任务和微任务在意义上便没有区别了。
3.宏任务与微任务首先我们肯定要坚持一点:宏任务和微任务在意义上肯定是有绝对区别的。
看一下在浏览器环境下能够触发宏任务的操作都有哪些(其他环境下会有不同):
以 setTimeout 为例。由于 JavaScript 是单线程,所以 setTimeout 的计时操作一定不是JavaScript来做的,否则会造成代码执行的阻塞。
那么这种操作是由谁来做的?是宿主环境。以浏览器为例子,JavaScript 在执行到 setTimeout 时会告诉浏览器:“Hey boy!这有个定时器,你帮我看着点,等到点了你告诉我一下”。这时候浏览器就会进行一个计时操作,计时完成以后,将 setTimeout 的回调放入 JavaScript 事件循环的回调队列中。这样 JavaScript 就可以在接下来的执行中处理这个回调。
我们看一下上面列出来的4点触发宏任务的操作,全部与浏览器相关!
所以,我个人的理解是:宏任务便是 JavaScript 与宿主环境产生的回调,需要宿主环境配合处理并且会被放入回调队列的任务都是宏任务。
浏览器下触发微任务的操作为:
这两个操作也都能够产生异步操作,那为什么与宏任务不一样呢。这里就要涉及到事件循环的另一个队列了--作业队列(微任务队列)。
为了更好的理解作业队列,我们把执行队列从开始到结束这样的一个过程,称为一个tick,回调队列的第一个事件则会在下一个tick中被执行,第二个事件会在下下个tick中...这样依次执行。
而作业队列则是位于当前tick的最尾部,在当前tick中添加的微任务都不会留到下一个tick,而是在tick的尾部触发执行。
一个事件循环中,在执行队列里的任务执行完毕以后,会有一个单独的步骤,叫 Perform a microtask checkpoint,即执行微任务检查点。这个操作是检查作业队列中是否有微任务,如果有,便将作业队也会当作执行队列来继续执行,完毕后将执行队列置空。
所以,这里我们就可以确定的说:同一个执行队列产生的微任务总是会在宏任务之前被执行
那么,我们现在回答第三点开始提出来的问题,宏任务和微任务的意义区别在哪呢?
个人的理解是宏任务是能够在宿主环境的协助下,通过回调队列来完成异步操作,微任务则是在宏任务执行前,进行某些操作,告诉 Javascript 引擎在处理完当前执行队列后,尽快地执行我们的代码。
4.关于requestAnimationFrame起初我对requestAnimationFrame的定义是宏任务,因为在测试requestAnimationFrame的时候我用了下面这段代码




[JavaScript] 纯文本查看 复制代码
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来写” --- 阿特伍德定律
附补充
[JavaScript] 纯文本查看 复制代码
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)

文章转载自:https://juejin.im/post/5caac21de51d452b66462649






欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2