A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

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




[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里面的一些基本知识
  • 关于代码执行环境,JavaScript代码执行时,引擎会创造出来当前代码块的执行环境,在涉及到使用变量时,只能查找到当前环境的变量和包含当前执行环境的外部环境变量。全局环境是最外层的执行环境
  • JavaScript是单线程
  • JavaScript在处理异步操作时,利用的是事件循环机制。
2.宏任务、微任务与事件循环机制了解事件循环的同学都知道,在事件循环中,异步事件并不会放到当前任务执行队列,而是会被挂起,放入另外一个回调队列。当前的任务队列执行结束以后,JavaScript引擎回去检查回调队列中是否有等待执行的任务,若有会把第一个任务加入执行队列,然后不断的重复这个过程。
从现象上来看,宏任务和微任务产生的异步操作,都会在执行队列完成后再执行,所以貌似宏任务和微任务都放到回调队列中。
真的是这样吗?
肯定不是。如果真的是这样,那宏任务和微任务在意义上便没有区别了。
3.宏任务与微任务首先我们肯定要坚持一点:宏任务和微任务在意义上肯定是有绝对区别的。
看一下在浏览器环境下能够触发宏任务的操作都有哪些(其他环境下会有不同):
  • I/O 操作
  • setTimeout
  • setInterval
  • requestAnimationFrame(争议,后面会讨论)
以 setTimeout 为例。由于 JavaScript 是单线程,所以 setTimeout 的计时操作一定不是JavaScript来做的,否则会造成代码执行的阻塞。
那么这种操作是由谁来做的?是宿主环境。以浏览器为例子,JavaScript 在执行到 setTimeout 时会告诉浏览器:“Hey boy!这有个定时器,你帮我看着点,等到点了你告诉我一下”。这时候浏览器就会进行一个计时操作,计时完成以后,将 setTimeout 的回调放入 JavaScript 事件循环的回调队列中。这样 JavaScript 就可以在接下来的执行中处理这个回调。
我们看一下上面列出来的4点触发宏任务的操作,全部与浏览器相关!
所以,我个人的理解是:宏任务便是 JavaScript 与宿主环境产生的回调,需要宿主环境配合处理并且会被放入回调队列的任务都是宏任务。
浏览器下触发微任务的操作为:
  • Promise
  • MutationObserver
这两个操作也都能够产生异步操作,那为什么与宏任务不一样呢。这里就要涉及到事件循环的另一个队列了--作业队列(微任务队列)。
为了更好的理解作业队列,我们把执行队列从开始到结束这样的一个过程,称为一个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来写” --- 阿特伍德定律
附补充
  • Node环境下宏任务:
    • I/O 操作
    • setTimeout
    • setInterval
    • setImmediate
  • Node环境下微任务:
    • Promise
    • process.nextTick
  • 增加了while循环进行延时,你能对事件循环感受的更清楚:
[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

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马