? 由于這兩天面試有遇到相關(guān)的問(wèn)題忽冻,以及在維護(hù)外包項(xiàng)目時(shí)遇到的種種相關(guān)的奇葩異步亂用的問(wèn)題,決定好好捋捋這幾個(gè)名詞在實(shí)際中的應(yīng)用此疹。
一甚颂、隊(duì)列類(lèi)型
? js是單線程編程語(yǔ)言蜜猾,所以js的執(zhí)行順序是按語(yǔ)句的順序去排列的。
js的執(zhí)行任務(wù)可以分為兩類(lèi):
- 同步任務(wù):就是在主線程上的任務(wù)振诬,順序到達(dá)后馬上執(zhí)行蹭睡;
- 異步任務(wù):在主線程上異步執(zhí)行的任務(wù),順序到達(dá)后并不會(huì)馬上執(zhí)行赶么,但會(huì)被排在任務(wù)隊(duì)列里肩豁,執(zhí)行完同步任務(wù)后按隊(duì)列執(zhí)行異步任務(wù)。
二辫呻、執(zhí)行
不管理沒(méi)理解清钥,不廢話,直接剛:
1放闺、下面先看同步任務(wù):A
console.log('start');
function task() {
console.log('task');
}
task();
console.log('end');
在控制臺(tái)可以看到輸出start task end
祟昭,這就是同步任務(wù),只要順序到達(dá)馬上執(zhí)行怖侦。
2篡悟、接著看異步任務(wù)
異步任務(wù)有ES5的settimeout、setinterval
以及ES6的promise
匾寝。
settimeout
接著上面的代碼搬葬,先看前者:B
console.log('start');
setTimeout(() => {
console.log('s1');
});
function task() {
console.log('task');
}
task();
console.log('end');
可以看到,盡管settimeout在task函數(shù)的前面艳悔,但s1在最后輸出急凰,表明settimeout是異步任務(wù),排在主線程之外的隊(duì)列中執(zhí)行猜年。
? 為了進(jìn)一步驗(yàn)證抡锈,我們?cè)黾与y度,看下面代碼:C
console.log('start');
setTimeout(() => {
console.log('s1');
});
function task() {
console.log('task');
setTimeout(() => {
console.log('s2');
});
}
task();
setTimeout(() => {
console.log('s3');
});
console.log('end');
? 刷新瀏覽器乔外,可以看到控制臺(tái)在最后按順序輸出s1 s2 s3
床三,這表明所有的settimeout
事件在同一隊(duì)列里,所以隊(duì)列里的settimeout按順序執(zhí)行袁稽。
? 在日常開(kāi)發(fā)過(guò)程中我們經(jīng)常會(huì)遇到異步嵌套異步勿璃,如果同個(gè)隊(duì)列內(nèi)部都有異步擒抛,這時(shí)候的執(zhí)行又是怎樣的呢推汽?接下來(lái)繼續(xù)增加難度:D
console.log('start');
setTimeout(() => {
console.log('s1');
setTimeout(() => {
console.log('s4');
});
});
function task() {
console.log('task');
setTimeout(() => {
console.log('s2');
});
}
task();
setTimeout(() => {
console.log('s3');
setTimeout(() => {
console.log('s5');
});
});
console.log('end');
?上面我們?cè)趦蓚€(gè)settimeout里分別新增了一個(gè)settimeout,這時(shí)候的執(zhí)行順序會(huì)不會(huì)有什么不同呢歧沪?
?繼續(xù)刷新瀏覽器歹撒,在控制臺(tái)看輸出...end s1...s5
,怎么樣诊胞,有沒(méi)有覺(jué)得很奇怪暖夭?
?如果不理解js的任務(wù)隊(duì)列執(zhí)行順序問(wèn)題锹杈,會(huì)對(duì)上面的代碼執(zhí)行結(jié)果表示一臉萌,起碼當(dāng)初的我就是這種表情迈着。
?所以接著C的思路在D的體現(xiàn):主線程任務(wù)先執(zhí)行竭望,異步任務(wù)推入任務(wù)隊(duì)列,主線程任務(wù)執(zhí)行完成之后按順序繼續(xù)執(zhí)行任務(wù)隊(duì)列的任務(wù)裕菠;在任務(wù)隊(duì)列里有二維異步任務(wù)咬清,推入第二條隊(duì)列,執(zhí)行完第一隊(duì)列后奴潘,繼續(xù)執(zhí)行第二隊(duì)列旧烧;
?看到這里,應(yīng)該對(duì)js的任務(wù)隊(duì)列有一定的理解了吧画髓,如果還不理解掘剪,就按照上面的例子換著法子使勁折騰就對(duì)了,實(shí)踐出真知奈虾,在學(xué)習(xí)編程的時(shí)候是最最真的道理了夺谁。
?看完settimeout
的例子,接下來(lái)我們繼續(xù)看 promise
愚墓,至于setinterval
就暫時(shí)不討論了予权。
promise
為了循序漸進(jìn),我們接著B的例子一點(diǎn)點(diǎn)增加難度浪册,繼續(xù):E
console.log('start');
setTimeout(() => {
console.log('s1');
});
function task() {
console.log('task');
}
new Promise(resolve => {
console.log('p1');
resolve(true);
});
task();
console.log('end');
? 刷新瀏覽器可以看到輸出start p1 task end s1
扫腺,為啥?
? 原因所在村象,就要引申出宏任務(wù)和微任務(wù)的概念了笆环。
? 如果對(duì)js的事件循環(huán)機(jī)制理解不深,到這里或許就要懵了厚者,既有同步任務(wù)異步任務(wù)還有任務(wù)隊(duì)列躁劣,現(xiàn)在又多了了宏觀任務(wù)和微觀任務(wù),這咋判斷库菲?接下來(lái)我就講講這幾個(gè)概念的...我也講不清楚账忘,所以我就找了一段網(wǎng)上我覺(jué)得描述的比較好的:
js是單線程語(yǔ)言,對(duì)于異步操作只能先把它放在一邊熙宇,按照某種規(guī)則按先后順序放進(jìn)一個(gè)容器(其實(shí)就是存入宏觀任務(wù)和微觀任務(wù)隊(duì)列中)鳖擒,先處理同步任務(wù),再處理異步任務(wù)烫止。異步任務(wù)分為 [ 宏觀任務(wù)隊(duì)列蒋荚、微觀任務(wù)隊(duì)列 ]
按照規(guī)定,能發(fā)起宏觀任務(wù)的方法有:
script(整體代碼)馆蠕、setTimeout期升、setInterval惊奇、I/O、UI交互事件播赁、postMessage颂郎、MessageChannel、setImmediate(Node.js 環(huán)境)容为;
微觀任務(wù)的方法有:
Promise.then祖秒、MutaionObserver、process.nextTick(Node.js 環(huán)境)舟奠,async/await實(shí)際上是promise+generator的語(yǔ)法糖竭缝,也就是promise,也就是微觀任務(wù)沼瘫;
? 有promise就少不了then抬纸,所以在E基礎(chǔ)上我們加上then,再看輸出:F
console.log('start');
setTimeout(() => {
console.log('s1');
});
function task() {
console.log('task');
}
new Promise(resolve => {
console.log('p1');
resolve(true);
}).then(() => {
console.log('then');
});
task();
console.log('end');
? 之前的輸出是start p1 task end s1
耿戚,加了then之后輸出start p1 task end then s1
湿故,then在s1之前輸出。
? 按照上面對(duì)幾個(gè)概念的描述膜蛔,promise的執(zhí)行屬于微任務(wù)坛猪,settimeout屬于宏任務(wù),而F的輸出表明微任務(wù)先于宏任務(wù)執(zhí)行皂股∈裕可是,這是絕對(duì)的嗎呜呐?下面我們繼續(xù)作:G
console.log('start');
setTimeout(() => {
console.log('s1');
new Promise(resolve => {
console.log('p2');
resolve(true);
}).then(() => {
console.log('then2');
});
});
function task() {
console.log('task');
}
new Promise(resolve => {
console.log('p1');
resolve(true);
}).then(() => {
console.log('then');
});
task();
console.log('end');
? 可以看到新添加的微任務(wù)的結(jié)果p2 then2
在最后輸出就斤,這是不是跟上面“微任務(wù)先于宏任務(wù)執(zhí)行”有沖突?仔細(xì)一想蘑辑,其實(shí)不然洋机,在上面G的代碼執(zhí)行順序來(lái)看,可以分為幾個(gè)執(zhí)行步驟:
先執(zhí)行同步任務(wù):
start洋魂、p1绷旗、task、end
副砍,為何p1會(huì)先執(zhí)行衔肢?因?yàn)檫@時(shí)候的p1其實(shí)還在同步任務(wù)里,then之后的操作才在異步任務(wù)隊(duì)列中;接著執(zhí)行異步任務(wù),而異步任務(wù)分為宏任務(wù)和微任務(wù)入篮,微任務(wù)先于宏任務(wù)執(zhí)行,所以第二步執(zhí)行:
then启搂、s1
;-
最后執(zhí)行宏任務(wù)內(nèi)部的異步刘陶,也就是微任務(wù):
p2胳赌、then2
。? 所以從上面可以總結(jié):先執(zhí)行主線程的同步任務(wù)匙隔,這是第一梯隊(duì)疑苫;若有異步,先執(zhí)行異步里的微任務(wù)也就是then內(nèi)部的操作纷责,這是第二梯隊(duì)捍掺;然后執(zhí)行宏任務(wù)也就是settimeout內(nèi)部的操作,這是第三梯隊(duì)再膳;如果第三梯隊(duì)中又有微任務(wù)挺勿,繼續(xù)執(zhí)行,這是第四梯隊(duì)喂柒。
為了驗(yàn)證不瓶,我們繼續(xù)作:H
console.log('123');
setTimeout(() => {
console.log('s1');
new Promise(resolve => {
console.log('res3');
resolve(true);
}).then(() => {
console.log('then3');
});
});
setTimeout(() => {
console.log('s2');
}, 0);
function task() {
console.log('task');
setTimeout(() => {
console.log('s3');
});
new Promise(resolve => {
console.log('res2');
resolve(true);
}).then(() => {
console.log('then2');
});
}
new Promise(resolve => {
console.log('res');
setTimeout(() => {
console.log('ps');
});
resolve(true);
console.log('after');
}).then(() => {
console.log('then');
});
console.log('456');
task();
console.log('end');
? 看完上面的代碼,先別管結(jié)果如何灾杰,有沒(méi)有想吐槽:變態(tài)蚊丐!誰(shuí)會(huì)寫(xiě)這樣的代碼!你別不信艳吠,其實(shí)這是我在接外包項(xiàng)目debug的時(shí)候經(jīng)常遇到的事麦备。因?yàn)樗麄兊拇a往往是以完成任務(wù)為目標(biāo)而得出來(lái)的,至于完成的過(guò)程是怎樣的昭娩,他們完全不會(huì)關(guān)注泥兰,這就導(dǎo)致代碼內(nèi)部會(huì)出現(xiàn)如宏觀任務(wù)嵌套微任務(wù),微任務(wù)又與宏任務(wù)以及微任務(wù)相互并行的現(xiàn)象题禀,至于中間會(huì)出現(xiàn)什么問(wèn)題鞋诗,They don't care...扯遠(yuǎn)了,想知道上面的結(jié)果是啥迈嘹,自己看削彬。
async...await
? 最后看看promise的語(yǔ)法糖async await在隊(duì)列中又有什么不同, 下面繼續(xù)剛:I
async function async1() {
console.log('async1-start');
await async2();
console.log('async1-end');
}
async function async2() {
console.log('async2');
new Promise(function(resolve) {
console.log('resolve1');
resolve();
})
.then(() => {
console.log('then');
})
.then(() => {
console.log('then2');
});
}
console.log('script-start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
})
.then(function() {
console.log('promise2');
})
.then(function() {
console.log('promise3');
});
console.log('script-end');
? 上面代碼最后輸出是script-start async1-start async2 resolve1 promise1 script-end then async1-end promise2 then2 promise3 setTimeout
秀仲。
? 如果不看結(jié)果融痛,自己預(yù)想一遍,能得到正確的輸出嗎神僵?如果理解了代碼從A~H的意思雁刷,基本上是能得到正確答案的,下面我們梳理一下執(zhí)行順序:
-
同步任務(wù):
-
script-start先執(zhí)行--輸出
script-start
保礼; - 接著執(zhí)行asyn1()函數(shù)沛励,函數(shù)內(nèi)部的async1-start在函數(shù)的同步序列中责语,緊接著被輸出,由于async的緣故目派,后面的語(yǔ)句會(huì)被放到微任務(wù)隊(duì)列-1--輸出
async1-start
坤候; - 然后到async2()函數(shù),首先會(huì)輸出async2企蹭,接著由于await的原因使得當(dāng)前函數(shù)變?yōu)橥桨壮铮?strong>resolve1雖然在promise內(nèi)部,但還在主線程上谅摄,所以也馬上被輸出徒河,then放在微任務(wù)隊(duì)列-2中--輸出
async2 resolve1
; - 執(zhí)行完asyn1()函數(shù)的同步任務(wù)送漠,繼續(xù)向下執(zhí)行虚青,遇到
new Promise
,內(nèi)部的promise1
在主線程上螺男,馬上被輸出棒厘,then放在微任務(wù)隊(duì)列-3中--輸出promise1
; - 最后執(zhí)行輸出
script-end
-
script-start先執(zhí)行--輸出
-
異步任務(wù):
? 先執(zhí)行微任務(wù):
-
從上面執(zhí)行順序可知現(xiàn)在有微任務(wù)隊(duì)列1/2/3下隧,按理是按照數(shù)字順序執(zhí)行的奢人,但既然這節(jié)討論的是async awiat,它的作用就是把異步函數(shù)當(dāng)成同步處理淆院,也就是說(shuō)等當(dāng)前的異步執(zhí)行完之后何乎,才會(huì)繼續(xù)向下執(zhí)行,所以在這里是先執(zhí)行微任務(wù)2土辩,才會(huì)執(zhí)行微任務(wù)1支救,最后執(zhí)行微任務(wù)3;
至于第二層鏈?zhǔn)?strong>then拷淘,因?yàn)闆](méi)有async await語(yǔ)法讓它提前執(zhí)行各墨,所以放在第二梯隊(duì)的微任務(wù)里。
所以第一次微任務(wù)--輸出
then async1-end promise2
启涯; -
上面執(zhí)行完第一梯隊(duì)的微任務(wù)贬堵,接著執(zhí)行第二梯隊(duì)微任務(wù),也就是第二層then語(yǔ)句结洼,所以按順序輸出--
then2 promise3
黎做;執(zhí)行完微任務(wù),最后執(zhí)行宏任務(wù):
-
宏任務(wù)只有一個(gè)setTimeout松忍,所以最終輸出--
setTimeout
蒸殿。執(zhí)行完畢。
最后,如果把
await async2()
中的await
去掉宏所,又會(huì)發(fā)生什么酥艳?請(qǐng)自行驗(yàn)證...... -
總結(jié)
? 所以綜上所述,簡(jiǎn)單總結(jié)一句話就是:同步任務(wù)結(jié)束后楣铁,先處理微觀任務(wù)后處理宏觀任務(wù),宏觀任務(wù)內(nèi)部處理重復(fù)上述動(dòng)作更扁。
? 以上內(nèi)容個(gè)人實(shí)踐總結(jié)盖腕,如有不對(duì)歡迎拍磚