在介绍前端宏任务与微任务以前,先列出来一道题,一块看一下。html
console.log('1')
setTimeout(() => {
console.log('2')
})
new Promise((resolve, rejects) => {
console.log('3')
resolve()
}).then(() => {
console.log('4')
})
console.log(5)复制代码
诸位能够先给出来一个本身的答案,运行一下结果,看看是否与本身想的一致。前端
这里介绍一下JavaScript里面的一些基本知识web
了解事件循环的同窗都知道,在事件循环中,异步事件并不会放到当前任务执行队列,而是会被挂起,放入另一个回调队列。当前的任务队列执行结束之后,JavaScript引擎回去检查回调队列中是否有等待执行的任务,如有会把第一个任务加入执行队列,而后不断的重复这个过程。api
从现象上来看,宏任务和微任务产生的异步操做,都会在执行队列完成后再执行,因此貌似宏任务和微任务都放到回调队列中。promise
真的是这样吗?浏览器
确定不是。若是真的是这样,那宏任务和微任务在乎义上便没有区别了。bash
首先咱们确定要坚持一点:宏任务和微任务在乎义上确定是有绝对区别的。app
看一下在浏览器环境下可以触发宏任务的操做都有哪些(其余环境下会有不一样):webapp
以 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 引擎在处理完当前执行队列后,尽快地执行咱们的代码。
起初我对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中,因此不属于宏任务,但也不是微任务,排在微任务以后。
固然,这个问题若是有大佬能够赐教,欢迎评论区留言。/手动撒花 /手动撒花
微任务与宏任务是我在处理一个七星瓢虫的时候,偶然接触到的知识。整理完这份文章,感受对 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)复制代码