宏任务(macroTask)和微任务(microTask),都是JavaScript中异步中的一些概念,如果你对其还一头雾水,那就跟着我再捋一遍,加深一下印象。
老规矩,先上图:
image.png
梳理这块主要还是为了让自己对代码执行的逻辑能够更加的清晰,当然,某些原因的还是应对一些面试题。
我们来以题入手,这应该是一道比较常见的题了:
console.log(100)
setTimeout(()=>{
console.log(200)
})
Promise.resolve().then(()=>{
console.log(300)
})
console.log(400)
// 答案为:100 400 300 200
问的就是,这段代码执行后,打印出来的顺序是什么?如果你心中的结果跟答案不一样的话,不要慌,首先对于「同步异步」有些了解的可以看出,先打印出100 400肯定是没有毛病的了,问题应该就出在200跟300上,它俩之间300为什么要比200打印的早呢?
「异步跟出场顺序有关系,不同类型的异步跟出场顺序就没关系了」
这就是为什么setTimeout
在promise
之上,但是200却在300之后打印出来的原因。假如我在打印200的那个setTimeout
后立刻跟一个打印222的setTimeout
,那么在同样的setTimeout
之间,打印顺序是由你写的顺序决定的(「定时时间记得一致」)。
那这个「不同类型」,就是宏任务和微任务两种了。我们直接来看一看,哪些是宏任务,哪些是微任务:
❝微任务的执行时机要比宏任务早!(可以先记一哈,后面会继续说这点) Dom事件不是异步操作,但是它依赖了eventloop机制,所以也归在这点里了 ❞
可以看出,宏任务和微任务组合起来,就是我们说的异步。先直接记住结果,再去探究为什么,看到这里应该可以回头去做出来之前那个题了,setTimeout
是个宏任务,而pormise
是个微任务,「微任务要比宏任务执行的要早」,所以先打印出来300后打印200。
eventloop和DOM渲染
是与为什么宏任务比微任务执行的晚
有联系的,所以我们先来弄懂这个。再举个例子:
const div1 = $('<div>1234</div>')
const div2 = $('<div>1234</div>')
const div3 = $('<div>1234</div>')
const div4 = $('<div>1234</div>')
const div5 = $('<div>1234</div>')
$('#divs').append(div1).append(div2).append(div3).append(div4).append(div5)
console.log('length', $(divs).children().length) // 5
我们用js建几个div,之后添加到一个节点下,再立刻打印一下这个节点下子元素的个数,这几行执行完之后,我们可以看出打印了5,页面上也显示了五段话,这没有什么问题。
但是我们这里要get的一点是,「我们的眼睛是在什么时刻看到这五段话的」。
要了解的一点是:DOM渲染就是将DOM结构或者是js操作的内容渲染到浏览器上,让我们的眼睛可以看见。
其实如果只执行这一段js,到打印那行为止,我们是能打印出来5的,但是「此时此刻」我们是看不见页面上新增的那五段话的。所以我们需要时机去执行这个DOM渲染的过程,就要去了解一下eventloop的过程。
大概画了一下,在eventloop中主要有这么几个东西,我们再详细说一下它。
image.png
首先我们知道,js是单线程了,按照顺序一行一行执行,如果某行报错则停止后续执行,然后就是「先执行同步,再执行异步」,看图,我们会将同步代码一行一行放入Call Stack
中执行,遇到异步,就会移动到Web APIs
中记录下来,等待时机,如果时机到了,将其移动到Callback Queue
中,如果同步代码执行完,也就是Call Stack
为空,「这时候首先会尝试DOM渲染,之后再触发Event Loop
机制」,Event Loop
开始工作,轮询查找Callback Queue
,如果有就移动到Call Stack
中执行。
❝请注意:为什么是尝试DOM渲染,因为可能这一段js里并没有修改DOM,尝试是代表着如果有对DOM的操作,那么去渲染,没有的话,忽略这一步。 ❞
这段代码接着上面建立的那一堆DIV去执行,alert
会阻断js执行,也会阻断DOM渲染,利用这一点,我们可以直观的去看出谁先谁后和DOM渲染在什么时候执行的。
// 微任务:DOM渲染之前执行
Promise.resolve().then(() => {
const length = $('#divs').children().length
alert(`微任务 ${length}`)
})
// 宏任务:DOM渲染之后执行
setTimeout(() => {
const length = $('#divs').children().length
alert(`宏任务 ${length}`)
})
还是要从eventloop的角度来看,首先我们来看setTimeout
首先要进入Web APIs
中等待时机,完后进入Callback Queue
中,等待EventLoop
机制被触发之后执行,而尝试DOM渲染刚刚好,卡在了Call Stack
空闲下来,跟执行EventLoop
机制中间,这就是为什么setTimeout
在DOM渲染之后执行的原因
接着我们来看Promise.then
image.png
Promise
是ES6
规范的,不是W3C规范的所以不经过Web APIs
,此外与宏任务不同的一点是,有自己独特的micro task queue
,这是为什么呢?
所以最终我们的EventLoop
应该是这样:
image.png
当Call Stack
清空之后,首先执行当前的微任务
,再去尝试DOM渲染
,最后触发EventLoop机制
,执行宏任务。
学完了来看看自己会不会吧?
宏任务跟微任务是JavaScript异步中的一个大块,所以快来学习吧!
梳理好每一个知识点,稳扎稳打,才不会被面试官问倒😰~
如果文章有误欢迎在评论区指出,感谢指正🔔
这是我面试专栏的第二篇文章,后续会陆陆续续继续整理的,欢迎大家关注📢