搞懂JS的執(zhí)行流程對(duì)復(fù)雜代碼的理解和優(yōu)化還是非常重要的。首先JS和node中都是基于事件循環(huán)的。
JS的單線程:我的理解是JS執(zhí)行環(huán)境棧(ECS)中同一時(shí)刻只能執(zhí)行一個(gè)任務(wù),不能并行執(zhí)行多個(gè)任務(wù)。所以JS是單線程的。但是JS依然可以執(zhí)行異步代碼,是因?yàn)闉g覽器多線程輔助執(zhí)行了程序。
事件循環(huán) 就是ECS中多個(gè)狀態(tài)(執(zhí)行任務(wù),等待任務(wù),休眠)無(wú)限切換的循環(huán)。瀏覽器的多個(gè)線程協(xié)同工作(比如有的線程在監(jiān)聽(tīng)ajax是否返回?cái)?shù)據(jù),有的線程在給定時(shí)器計(jì)時(shí)),保證ECS中單線程的執(zhí)行任務(wù)。
引擎在執(zhí)行任務(wù)時(shí)永遠(yuǎn)都不會(huì)進(jìn)行DOM的渲染或者更改,如果一項(xiàng)任務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),瀏覽器就會(huì)拋出"頁(yè)面未響應(yīng)"之類的警報(bào)。
微任務(wù):一般指promise,MutationObserver
宏任務(wù):微任務(wù)以外的就是宏任務(wù)了,比如:ajax請(qǐng)求,函數(shù),事件,setTimeout,setInterval,requestAnimationFrame等
每個(gè)宏任務(wù)之后,引擎會(huì)立即執(zhí)行微任務(wù)隊(duì)列中的所有任務(wù),然后再執(zhí)行其他的宏任務(wù),或渲染,或進(jìn)行其他任何操作。就是說(shuō)微任務(wù)會(huì)在執(zhí)行任何其他事件處理,或渲染,或執(zhí)行任何其他宏任務(wù)之前完成。
知道什么是宏任務(wù),什么是微任務(wù),知道事件循環(huán),知道JS引擎先執(zhí)行同步代碼,再執(zhí)行微任務(wù),再執(zhí)行宏任務(wù),無(wú)限循環(huán)執(zhí)行。這樣就能夠理解JS的執(zhí)行流程了。
JS是解釋型語(yǔ)言,代碼是從上往下,從左至右逐行逐字執(zhí)行的。遇到同步代碼就直接交給ECS執(zhí)行,遇到宏任務(wù)就交給工作線程去工作,比如定時(shí)器開(kāi)始計(jì)時(shí),監(jiān)聽(tīng)ajax是否返回?cái)?shù)據(jù)等等。時(shí)間到了或者數(shù)據(jù)返回了就放到宏任務(wù)隊(duì)列中,等待ECS執(zhí)行。遇到微任務(wù)就放入到微任務(wù)隊(duì)列中,等到ECS執(zhí)行。同步代碼執(zhí)行完了之后先清空微任務(wù)列表,然后再執(zhí)行宏任務(wù)。如此循環(huán)執(zhí)行就是JS的執(zhí)行流程。
接下來(lái)看幾個(gè)例子:
一、
setTimeout(() => alert("timeout"));
Promise.resolve() .then(() => alert("promise"));
alert("code");
// 他的執(zhí)行順序是:先code,因?yàn)槭峭酱a(主程序),然后是promise,因?yàn)閳?zhí)行完了宏任務(wù)就會(huì)清空微任務(wù)隊(duì)列,然后再執(zhí)行timeout,因?yàn)樗呛耆蝿?wù)。
二、 平常所說(shuō)的異步,也不是說(shuō)JS開(kāi)了另外的線程去執(zhí)行,而是等同步的代碼執(zhí)行完畢以后,再同步的執(zhí)行那段所謂的異步代碼。JS同一個(gè)時(shí)間節(jié)點(diǎn)只能做 一件事情 所以你設(shè)置的定時(shí)任務(wù),不是嚴(yán)格按照你設(shè)想的時(shí)間執(zhí)行。比如:
let d1 = Date.now();
let d2 = Date.now();
while(d2 - d1 < 500) { // 這里耽誤了五秒鐘
d2 = Date.now();
}
setTimeout(() => {
console.log("定時(shí)器任務(wù)"); // 所該輸出是在10秒之后輸出
}, 5000);
三、
// 異步任務(wù)計(jì)時(shí)是從什么時(shí)候開(kāi)始算的呢?
let d1 = Date.now();
let d2 = Date.now();
setTimeout(() => { // 執(zhí)行到宏任務(wù) 會(huì)交給其他線程去計(jì)時(shí) 到時(shí)間再放入到宏任務(wù)隊(duì)列中等待執(zhí)行
console.log("定時(shí)器任務(wù)") // 會(huì)在六秒鐘之后立即輸出
}, 4000);
while(d2 - d1 < 6000) {
d2 = Date.now();
}
四、
const str = "主程序";
setTimeout(() => { // ①
Promise.resolve().then(() => {
console.log("第一個(gè)零延時(shí)定時(shí)任務(wù)中的微任務(wù)");
})
console.log("第一個(gè)零延時(shí)的定時(shí)任務(wù)");
setTimeout(() => {
console.log("第一個(gè)零延時(shí)定時(shí)任務(wù)中的零延時(shí)定時(shí)任務(wù)")
});
});
setTimeout(() => { // ②
console.log("我是第二個(gè)零延時(shí)的定時(shí)任務(wù)");
});
Promise.resolve().then(() => {
console.log("我是外部的微任務(wù)");
})
console.log(str);
// 代碼從上往下執(zhí)行 遇到定時(shí)器① 就交給工作線程去計(jì)時(shí) 然后往下走 遇到了定時(shí)器② 也交給工作線程去計(jì)時(shí) 時(shí)間到了一次放入到宏任務(wù)隊(duì)列中
// 往下走遇到了微任務(wù)Promise 放入微任務(wù)隊(duì)列 再往下執(zhí)行主程序
// 所以先輸出 一、主程序
// 主程序執(zhí)行完了 要先清空微任務(wù)隊(duì)列 所以 輸出 二、我是外部的微任務(wù)
// 清空完了微任務(wù)列表 就 開(kāi)始執(zhí)行宏任務(wù)隊(duì)列中的宏任務(wù) 繼續(xù)執(zhí)行: 同步代碼 => 清空微任務(wù)隊(duì)列 => 宏任務(wù)隊(duì)列中的宏任務(wù) => 清空微任務(wù)隊(duì)列 => ...
// 所以輸出 三、第一個(gè)零延時(shí)的定時(shí)任務(wù)
// 輸出 四、第一個(gè)零延時(shí)定時(shí)任務(wù)中的微任務(wù)
// 輸出 五、我是第二個(gè)零延時(shí)的定時(shí)任務(wù)
// 最后輸出 六、第一個(gè)零延時(shí)定時(shí)任務(wù)中的零延時(shí)定時(shí)任務(wù)