
首先,請(qǐng)記住兩點(diǎn):
- JS 是單線程語(yǔ)言
- JS 的Event Loop 是JS的執(zhí)行機(jī)制.要想深入理解JS的執(zhí)行,就得深入了解JS 的 Event Loop
JS 為什么是單線程? 為什么需要異步? 單線程又是如何實(shí)現(xiàn)異步的呢?
1. JS 為什么是單線程
JS 最初被設(shè)計(jì)用在瀏覽器中,那么想象一下,如果瀏覽器中的 JS 是多線程的
場(chǎng)景描述:
假設(shè)現(xiàn)在有兩個(gè)線程,process1和process2,由于是多線程的 JS ,所以它們對(duì)同一個(gè)DOM同時(shí)進(jìn)行操作
process1刪除了改DOM,而process2編輯了改DOM,同時(shí)下達(dá)了兩個(gè)矛盾的命令,瀏覽器到底該如何執(zhí)行呢?
這樣想,JS 為什么會(huì)被設(shè)計(jì)成單線程應(yīng)該容易理解了吧
2. JS 為什么需要異步
場(chǎng)景描述:
如果 JS 中不存在異步,代碼只能自上而下的執(zhí)行,這個(gè)時(shí)候,假如上一行代碼的解析時(shí)間過(guò)長(zhǎng),那么下一
行代碼就會(huì)被阻塞,而對(duì)于用戶而言,阻塞就意味著"卡死",這樣就導(dǎo)致了很差的用戶體驗(yàn)
所以,JS中需要異步執(zhí)行
3. JS 單線程又是如何實(shí)現(xiàn)異步的呢
是通過(guò)事件循環(huán)(event loop)來(lái)實(shí)現(xiàn)的,理解了event loop機(jī)制,就理解了 JS 的執(zhí)行機(jī)制
JS 中的event loop (1)
例1,觀察下面代碼的執(zhí)行順序
console.log(1)
setTimeout(() => console.log(2),0)
console.log(3)
//運(yùn)行結(jié)果是:1 3 2
也就是說(shuō),setTimeout里的代碼并沒(méi)有立即執(zhí)行,而是延遲了一段時(shí)間,滿足一定條件后才去執(zhí)行,這類(lèi)代碼,我們稱之為異步代碼
所以,這里我們知道了 JS 把任務(wù)分類(lèi)的一種方式: 同步任務(wù)和異步任務(wù)
按照這種分類(lèi)方式, JS 的執(zhí)行機(jī)制是:
- 首先判斷 JS 是同步還是異步,同步就進(jìn)入主線程,異步就進(jìn)入 event table
- 異步任務(wù)在event table中注冊(cè)函數(shù),當(dāng)滿足觸發(fā)條件后,就推入event queue
- 同步任務(wù)進(jìn)入主線程后一直執(zhí)行,直到主線程空閑時(shí),才會(huì)去event queue中查看有否有可執(zhí)行的異步任務(wù),如果有,就推入主線程中
以上三步循環(huán)執(zhí)行,這就是event loop
JS 中的event loop (2)
以上關(guān)于event loop的解釋就是我對(duì)于JS執(zhí)行機(jī)制的理解,直到我遇到了下面這行代碼
setTimeou(() => console.log('代碼開(kāi)始執(zhí)行'),0)
new Promise((resolve,reject) => {
console.log('開(kāi)始for循環(huán)');
for(let i=0;i<10000;i++){
i == 99 && resolve()
}
}).then(() => console.log('執(zhí)行then函數(shù)'))
console.log('代碼執(zhí)行結(jié)束');
嘗試按照上文我們剛學(xué)到的 JS 執(zhí)行機(jī)制去分析
猜測(cè)結(jié)果是:開(kāi)始for循環(huán) -- 代碼執(zhí)行結(jié)束 -- 代碼開(kāi)始執(zhí)行 -- 執(zhí)行then函數(shù)
然而實(shí)際結(jié)果卻是:開(kāi)始for循環(huán) -- 代碼執(zhí)行結(jié)束 -- 執(zhí)行then函數(shù) -- 開(kāi)始for循環(huán)
事實(shí)上,JS 執(zhí)行機(jī)制按照同步和異步的劃分方式,并不準(zhǔn)確
準(zhǔn)確的劃分方式是:
- macro - task(宏任務(wù)):包括整體script代碼,setTimeout,setInterval
- micro - task(微任務(wù)):promise,process.nextTick
image
按照這種分類(lèi)方式,JS 的執(zhí)行機(jī)制是: - 執(zhí)行一個(gè)宏任務(wù),過(guò)程中如果遇到微任務(wù),就將其放到微任務(wù)的[事件隊(duì)列]里
- 當(dāng)前宏任務(wù)執(zhí)行結(jié)束后,會(huì)查看微任務(wù)的[事件隊(duì)列],并將里面全部的微任務(wù)依次執(zhí)行完
重復(fù)執(zhí)行以上兩步,結(jié)合event loop(1) 和 event loop(2) 就是更為準(zhǔn)確的 JS 執(zhí)行機(jī)制了
現(xiàn)在我們回過(guò)頭來(lái)分析例2:

關(guān)于setTimeout
下面這段代碼是什么意思? 我們一般說(shuō):3秒后,執(zhí)行setTimeout里面的函數(shù)
setTimeout(() => {
console.log('執(zhí)行代碼');
})
但是這種說(shuō)話并不嚴(yán)謹(jǐn),正確的解釋是:3秒后,setTimeout中的函數(shù)會(huì)被推入event queue,而event queue(事件隊(duì)列)里的任務(wù),只有在主線程空閑時(shí)才會(huì)執(zhí)行.
所以只有滿足 ① 三秒后, ② 主線程空閑,3秒后才會(huì)執(zhí)行該函數(shù)
如果主線程執(zhí)行內(nèi)容很多,執(zhí)行時(shí)間超過(guò)了3秒,比如執(zhí)行了10秒,那么這個(gè)函數(shù)只有在10秒后執(zhí)行了