前端 | JS引擎的執(zhí)行機(jī)制

image.png

首先,請(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:


image.png

關(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í)行了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容