本文原創(chuàng):liuwan
在說(shuō)微任務(wù)與宏任務(wù)之前我們先說(shuō)一下同步任務(wù)與異步任務(wù)的概念吧陪每。
同步任務(wù)與異步任務(wù)
JavaScript語(yǔ)言的一大特點(diǎn)就是單線程,也就是說(shuō)筋帖,同一個(gè)時(shí)間只能做一件事哩都。單線程就意味著瞎颗,所有任務(wù)需要排隊(duì)月洛,前一個(gè)任務(wù)結(jié)束何恶,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng)膊存,后一個(gè)任務(wù)就不得不一直等著导而。
如果排隊(duì)是因?yàn)橛?jì)算量大忱叭,CPU忙不過(guò)來(lái)隔崎,倒也算了今艺,但是很多時(shí)候CPU是閑著的,因?yàn)镮O設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù))爵卒,不得不等著結(jié)果出來(lái)虚缎,再往下執(zhí)行。
JavaScript語(yǔ)言的設(shè)計(jì)者意識(shí)到钓株,這時(shí)主線程完全可以不管IO設(shè)備实牡,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù)轴合。等到IO設(shè)備返回了結(jié)果创坞,再回過(guò)頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去受葛。
于是题涨,所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous)总滩,另一種是異步任務(wù)(asynchronous)纲堵。同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù)闰渔,只有前一個(gè)任務(wù)執(zhí)行完畢席函,才能執(zhí)行后一個(gè)任務(wù);異步任務(wù)指的是冈涧,不進(jìn)入主線程茂附、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù),只有"任務(wù)隊(duì)列"通知主線程督弓,某個(gè)異步任務(wù)可以執(zhí)行了营曼,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。
具體來(lái)說(shuō)咽筋,異步執(zhí)行的運(yùn)行機(jī)制如下溶推。(同步執(zhí)行也是如此,因?yàn)樗梢员灰暈闆](méi)有異步任務(wù)的異步執(zhí)行奸攻。)
1.所有同步任務(wù)都在主線程上執(zhí)行蒜危,形成一個(gè)執(zhí)行棧(execution context stack)。
2.主線程之外睹耐,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)辐赞。只要異步任務(wù)有了運(yùn)行結(jié)果,在"任務(wù)隊(duì)列"之中放置一個(gè)事件硝训。
3.一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢响委,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列"新思,看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù)赘风,于是結(jié)束等待狀態(tài)夹囚,進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行邀窃。
4.主線程不斷重復(fù)上面的3荸哟。
以上摘自廖雪峰的博客 JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop.
問(wèn)題
我們先看一下下面的代碼,然后思考一下輸出的先后順序
setTimeout(() =>{
console.log('1')
});
new Promise((resolve) => {
console.log('2');
resolve();
}).then(() => {
console.log('3')
});
console.log('4');
按照同步與異步的概念來(lái)看瞬捕,輸出順序應(yīng)該是2鞍历、4、1肪虎、3.
但是劣砍,打開(kāi)控制臺(tái),輸入代碼扇救,查看輸出刑枝,順序是這樣的2、4爵政、3仅讽、1,發(fā)生了什么钾挟?
想知道發(fā)生了什么就繼續(xù)往下看吧洁灵。
宏任務(wù)與微任務(wù)
除了廣義的同步任務(wù)和異步任務(wù),我們對(duì)任務(wù)有更精細(xì)的定義掺出,分為宏任務(wù)和微任務(wù)徽千。
- 宏任務(wù):包括整體代碼script,setTimeout汤锨,setInterval双抽;
- 微任務(wù):Promise,process.nextTick
注:Promise立即執(zhí)行闲礼,then函數(shù)分發(fā)到“microtask”隊(duì)列牍汹,process.nextTick分發(fā)到“microtask”隊(duì)列
js引擎會(huì)把宏任務(wù)和微任務(wù)放置兩個(gè)“任務(wù)隊(duì)列”中,分別是“macrotask”隊(duì)列以及“microtask”隊(duì)列柬泽。在執(zhí)行異步任務(wù)時(shí)慎菲,先執(zhí)行宏任務(wù),然后在執(zhí)行微任務(wù)锨并。
所以現(xiàn)在解釋一下上面問(wèn)題中的輸出順序問(wèn)題:
- 這段代碼作為宏任務(wù)露该,進(jìn)入主線程
- 遇到setTimeout,把它的回掉函數(shù)放置“macrotask”隊(duì)列中第煮,然后接著執(zhí)行下面的代碼
- 遇到Promise解幼,new Promise會(huì)立即執(zhí)行抑党,于是輸出2,其then函數(shù)會(huì)被放置“microtask”隊(duì)列
- 遇到console.log('4')直接就執(zhí)行了
- 整體代碼script作為宏任務(wù)已經(jīng)執(zhí)行結(jié)束撵摆,判斷“microtask”隊(duì)列中是否有可執(zhí)行的微任務(wù)(then函數(shù))底靠,然后執(zhí)行,輸出3
- 至此台汇,整個(gè)代碼的第一輪循環(huán)結(jié)束了苛骨,要開(kāi)始下一輪循環(huán)篱瞎,先去查看“macrotask”隊(duì)列苟呐,有setTimeout的回掉函數(shù),然后執(zhí)行俐筋,執(zhí)行結(jié)束牵素,輸出1。
- 結(jié)束澄者。
所以上述問(wèn)題的輸出順序知道怎么肥死了吧笆呆?
盜一張事件循環(huán),宏任務(wù)粱挡,微任務(wù)的關(guān)系圖赠幕,如下:
圖說(shuō)明:進(jìn)入整體代碼(宏任務(wù))后,開(kāi)始第一次循環(huán)询筏。接著執(zhí)行所有的微任務(wù)榕堰。然后再次從宏任務(wù)開(kāi)始,找到符合執(zhí)行條件的一個(gè)宏任務(wù)執(zhí)行完畢嫌套,再執(zhí)行所有的微任務(wù)逆屡。
復(fù)習(xí)一下上面的知識(shí)點(diǎn),我們瞅一眼以下代碼:
console.log('1');
setTimeout(() => {
console.log('2');
process.nextTick(() => {
console.log('3');
})
setTimeout(() => {
console.log('10')
new Promise((resolve) => {
console.log('11');
resolve();
}).then(() => {
console.log('12')
})
})
new Promise((resolve) => {
console.log('4');
resolve();
}).then(() => {
console.log('5')
})
})
process.nextTick(() => {
console.log('6');
})
new Promise((resolve) => {
console.log('7');
resolve();
}).then(() => {
console.log('8')
setTimeout(() => {
console.log('9')
})
})
console.log('10')
大聲說(shuō)出答案吧:1踱讨、7魏蔗、10、6痹筛、8莺治、2、4帚稠、3谣旁、5、9翁锡、10蔓挖、11、12
好吧馆衔,我們分析一下:
- 整段代碼作為宏任務(wù)瘟判,進(jìn)入主線程
- 遇到console.log('1')怨绣,立即執(zhí)行,并向下執(zhí)行
- 遇到setTimeout拷获,把它的回掉函數(shù)
fn1
篮撑,放置“macrotask”隊(duì)列中,接著執(zhí)行下面的代碼 - 遇到process.nextTick匆瓜,把其回調(diào)函數(shù)
fn2
放置“microtask”隊(duì)列 - 遇到Promise赢笨,new Promise會(huì)立即執(zhí)行,于是輸出7驮吱,其then函數(shù)
fn3
會(huì)被放置“microtask”隊(duì)列 - 遇到console.log('10')直接就執(zhí)行了
- 整體代碼script作為宏任務(wù)已經(jīng)執(zhí)行結(jié)束茧妒,判斷“microtask”隊(duì)列中是否有可執(zhí)行的微任務(wù)(
fn2
以及fn3
),隊(duì)列具體先進(jìn)先出的特點(diǎn)左冬,所以先執(zhí)行fn2
桐筏,輸出6,然后執(zhí)行fn3
拇砰,輸出8梅忌,里面包含setTimeout,把它的回調(diào)函數(shù)fn4
放置“macrotask”隊(duì)列中除破。 - 至此牧氮,整個(gè)代碼的第一輪循環(huán)結(jié)束了,要開(kāi)始下一輪循環(huán)」宸悖現(xiàn)在“macrotask”隊(duì)列中有
fn1
踱葛、fn4
- 先去查看“macrotask”隊(duì)列,先執(zhí)行
fn1
躁垛。 - 執(zhí)行
fn1
剖毯,遇到console.log('2'),就輸出教馆,遇到process.nextTick逊谋,將其回調(diào)函數(shù)fn5
放置“microtask”隊(duì)列,遇到setTimeout土铺,把它的回掉函數(shù)fn6
放置“macrotask”隊(duì)列中胶滋,遇到Promise,new Promise會(huì)立即執(zhí)行悲敷,于是輸出4究恤,其then函數(shù)fn7
會(huì)被放置“microtask”隊(duì)列,即這個(gè)宏任務(wù)執(zhí)行完成后德。“macrotask”隊(duì)列里面有fn4
理张、fn6
雾叭。 - 現(xiàn)在檢查“microtask”隊(duì)列,里面有
fn5
暂幼、fn7
移迫,把里面的任務(wù)全部執(zhí)行完畢起意,
先執(zhí)行fn5
,輸出3,再執(zhí)行fn7
亲善,輸出5 - 至此逗柴,又一輪的循環(huán)結(jié)束了
- 再檢查“macrotask”隊(duì)列,里面有
fn4
琳水、fn6
,執(zhí)行fn4
造垛,輸出9. - 而現(xiàn)在的“microtask”隊(duì)列是空的顶瞒,再檢查“macrotask”隊(duì)列赃绊,有
fn6
- 執(zhí)行
fn6
羡榴,輸出10校仑,遇到new Promise者冤,輸出11涉枫,并把其回調(diào)函數(shù)fn8
放置“microtask”隊(duì)列愿汰,至此宏任務(wù)fn6
結(jié)束乐纸, - 檢查“microtask”隊(duì)列汽绢,并執(zhí)行
fn8
,輸出12跌宛,至此疆拘,“macrotask”隊(duì)列以及“microtask”隊(duì)列全部空了寂曹。 - 結(jié)束。