沒(méi)有異步機(jī)制會(huì)怎樣
js是單線程的,如果沒(méi)有異步機(jī)制,所有代碼會(huì)同步執(zhí)行。一些比較耗時(shí)的操作比如網(wǎng)絡(luò)請(qǐng)求,會(huì)阻塞線程直到請(qǐng)求返回結(jié)果才會(huì)繼續(xù)向下執(zhí)行。如果這樣js的執(zhí)行效率會(huì)比較低,甚至導(dǎo)致頁(yè)面卡死。所以必須加入異步機(jī)制以提升js線程執(zhí)行效率。
怎么加入異步機(jī)制
單線程和異步是互斥的,所以js線程本身是沒(méi)有異步能力的。但是瀏覽器賦予了js線程異步能力,瀏覽器不僅支持js主線程,還可以開(kāi)辟出http請(qǐng)求線程、定時(shí)器觸發(fā)線程、事件觸發(fā)線程等多種幕后線程。js線程遇到http請(qǐng)求、定時(shí)器等任務(wù),會(huì)把它們交給相應(yīng)的幕后線程執(zhí)行,js線程繼續(xù)向下執(zhí)行,http請(qǐng)求、定時(shí)器等任務(wù)完成后再把回調(diào)放入js隊(duì)列中等待js線程調(diào)用。這樣js線程就具備了異步的能力了。同時(shí)JS引入了Promise使單線程也具備了異步能力。
怎么調(diào)度隊(duì)列中的任務(wù)
瀏覽器把任務(wù)分為兩類(lèi):宏任務(wù)和微任務(wù)。js引擎線程發(fā)起的任務(wù)稱為微任務(wù),瀏覽器其它線程發(fā)起的任務(wù)稱為宏任務(wù)。整體代碼script會(huì)放到宏任務(wù)里;幕后線程執(zhí)行的任務(wù)完成后會(huì)把對(duì)應(yīng)的回調(diào)放到宏任務(wù)里;js主線程會(huì)把某些任務(wù)放到微任務(wù)里,比如:Promise.resolve().then(callback) js主線程會(huì)把callback放到微任務(wù)里。
js線程的一般調(diào)度策略是:js線程從宏任務(wù)中選取一個(gè)任務(wù)執(zhí)行,執(zhí)行完成后,逐個(gè)執(zhí)行微任務(wù)中的任務(wù)直至微任務(wù)為空,然后再次從宏任務(wù)中選取一個(gè)任務(wù)執(zhí)行,如此反復(fù)。js線程從宏任務(wù)中選取一個(gè)任務(wù)開(kāi)始到執(zhí)行完所有微任務(wù)為止稱為一個(gè)事件循環(huán)。比如宏任務(wù)中有兩個(gè)任務(wù)macro1, macro2,微任務(wù)中有三個(gè)任務(wù)micro1, micro2, micro3,那么調(diào)度順序?yàn)椋簃acro1 > micro1 > micro2 > micro3 > macro2。
分析一個(gè)例子:
setTimeout(function timout_1() {
console.log("timout_1");
}, 0);
Promise.resolve().then(function then_1() {
console.log("then_1");
});
Promise.resolve().then(function then_2() {
console.log("then_2");
});
js線程執(zhí)行前,宏任務(wù)里只有整體代碼script,微任務(wù)里沒(méi)有任務(wù)。按照一般調(diào)度策略:
- js線程首先從宏任務(wù)里選取整體代碼script執(zhí)行;
- 把setTimeout任務(wù)交給計(jì)時(shí)器線程,計(jì)時(shí)器線程會(huì)立刻往宏任務(wù)里加入一個(gè)tiemtimeou_1任務(wù) ;
- js線程把then_1加入微任務(wù);
- js線程把then_2加入微任務(wù) ;
- 執(zhí)行完宏任務(wù)里的整體代碼script,接著要依此執(zhí)行微任務(wù)里的任務(wù),現(xiàn)在微任務(wù)里有then_1和then_2,js線程從微任務(wù)里選取then_1執(zhí)行,控制臺(tái)打印then_1;
- js線程從微任務(wù)里選取then_2執(zhí)行,控制臺(tái)打印then_2;
- 微任務(wù)里沒(méi)有其它任務(wù),所以第一次事件循環(huán)結(jié)束;
- js線程開(kāi)啟第二次事件循環(huán),從宏任務(wù)中選取任務(wù)執(zhí)行。此時(shí)宏任務(wù)里只有timeou_1任務(wù),js線程選取timeou_1執(zhí)行,控制臺(tái)打印timeou_1;
- 執(zhí)行完timeou_1,接著要依此執(zhí)行微任務(wù)里的任務(wù),現(xiàn)在微任務(wù)里沒(méi)有任務(wù),所以第二次事件循環(huán)結(jié)束;
- 此時(shí)宏任務(wù)、微任務(wù)都為空,js線程循環(huán)等待新任務(wù)。
事件回調(diào)任務(wù)優(yōu)先級(jí)較高
宏任務(wù)中,任務(wù)一般會(huì)按照先進(jìn)先出的原則進(jìn)行調(diào)度,但是如果宏任務(wù)中同時(shí)存在事件觸發(fā)線程觸發(fā)的任務(wù),則會(huì)優(yōu)先調(diào)度該任務(wù)。
分析一個(gè)例子:
<!DOCTYPE html>
<html lang="en">
<body>
<button id="btn">button</button>
<script>
document.getElementById("btn").onclick = function handleClick() {
console.log("click btn");
};
setTimeout(function timeoutFn() {
console.log("timeoutFn");
}, 0);
document.getElementById("btn").click();
</script>
</body>
</html>
- js線程執(zhí)行整體代碼script;
- 為btn添加監(jiān)聽(tīng)事件;
- 時(shí)間觸發(fā)線程將timeoutFn加入宏任務(wù);
- 觸發(fā)點(diǎn)擊btn事件,將handleClick加入宏任務(wù);
- 執(zhí)行完整體代碼script,因?yàn)槲⑷蝿?wù)為空,本次事件循環(huán)結(jié)束;
- 第二次事件循環(huán),宏任務(wù)中同時(shí)存在時(shí)間觸發(fā)線程回調(diào)timeoutFn和事件觸發(fā)線程回調(diào)handleClick,雖然timeoutFn先進(jìn)入宏隊(duì)列但事件回調(diào)任務(wù)優(yōu)先級(jí)高,優(yōu)先調(diào)度handleClick,輸出click btn。微任務(wù)中沒(méi)有任務(wù)本次事件循環(huán)結(jié)束;
- 第三次事件循環(huán),宏任務(wù)中只有timeoutFn,js線程選取 timeoutFn 執(zhí)行,輸出 timeoutFn。微任務(wù)中沒(méi)有任務(wù)本次事件循環(huán)結(jié)束;
- 此時(shí)宏任務(wù)、微任務(wù)都為空,js線程循環(huán)等待新任務(wù)。
參考:
- 關(guān)鍵詞:
任務(wù)隊(duì)列,JS線程,異步API,后臺(tái)線程,瀏覽器多線程模型 - http://www.ruanyifeng.com/blog/2014/10/event-loop.html
- http://www.itdecent.cn/p/12b9f73c5a4f
- https://html.spec.whatwg.org/multipage/webappapis.html#event-loops