事件循環(huán)與任務(wù)隊(duì)列

[Toc]

為什么js是單線(xiàn)程?

  • 因?yàn)閖s的主要用途是在瀏覽器中操作DOM, 這決定了它只能是單線(xiàn)程,否則可能會(huì)造成同步的問(wèn)題. 試想一個(gè)線(xiàn)程對(duì)某個(gè)DOM節(jié)點(diǎn)上修改內(nèi)容, 另一個(gè)線(xiàn)程刪除這個(gè)DOM節(jié)點(diǎn), 那么兩個(gè)線(xiàn)程將會(huì)造成沖突.
  • 此外html5還新增了web worker, 它允許js創(chuàng)建多個(gè)線(xiàn)程. 但是子線(xiàn)程完全受主線(xiàn)程控制, 且子線(xiàn)程不允許操作DOM, 它沒(méi)有改變js單線(xiàn)程的本質(zhì).
  • 單線(xiàn)程必然造成阻塞, 但是js為了提高線(xiàn)程的利用率, 將需要耗費(fèi)時(shí)間的操作(比如ajax請(qǐng)求,dom操作等)交給宿主的webAPIs處理, 并繼續(xù)執(zhí)行接下來(lái)的代碼, 由宿主負(fù)責(zé)接收事件, 并將接收到的事件丟進(jìn)事件循環(huán)隊(duì)列, 等待主線(xiàn)程處理完執(zhí)行棧中最后一個(gè)作用域中的代碼后, 再將事件循環(huán)隊(duì)列中的事件的回調(diào)函數(shù)壓入執(zhí)行棧執(zhí)行.

事件循環(huán)

  • js代碼是分塊的, 且只有一個(gè)塊是現(xiàn)在執(zhí)行(執(zhí)行棧中最頂部的代碼), 其余的則會(huì)在將來(lái)執(zhí)行. 最常見(jiàn)的塊單位是函數(shù).

    • 為了方便理解可以將現(xiàn)在執(zhí)行代碼理解為同步代碼, 將來(lái)執(zhí)行代碼理解位異步代碼.
  • 任何時(shí)候, 只要把一段代碼包裝成函數(shù), 并指定它在響應(yīng)某個(gè)事件(定時(shí)器, 鼠標(biāo)點(diǎn)擊, ajax響應(yīng)等)時(shí)執(zhí)行, 就是在代碼中創(chuàng)建了一個(gè)將來(lái)執(zhí)行的塊.

  • 事件循環(huán)(eventLoop)是一種機(jī)制, 由宿主提供, 用來(lái)調(diào)用js引擎處理程序中的每個(gè)將來(lái)執(zhí)行塊的執(zhí)行

  • 事件循環(huán)偽代碼:

    • var eventLoop=[]
      var event
      while(true){
          // 一次tick
          if(eventLoop.length>0){
              event=eventLoop.shift()
              try{
                  event()
              }catch(err){
                  reportError(err)
              }
          }
      }
      
    • 不太完善的補(bǔ)充: 只有當(dāng)前執(zhí)行棧中的代碼全部執(zhí)行完畢后才會(huì)進(jìn)行事件循環(huán), eventLoop隊(duì)列中的事件添加由宿主的webAPIs決定

  • setTimeout()做的是: 在設(shè)定的delay時(shí)間后, 將回調(diào)函數(shù)插入事件循環(huán)隊(duì)列中. 該回調(diào)函數(shù)必須等到前面的項(xiàng)目處理完畢之后才會(huì)被調(diào)用, 也因此setTimeout()的回調(diào)函數(shù)的調(diào)用時(shí)間無(wú)法確定.

任務(wù)隊(duì)列(job queue)

  • 它是掛在事件循環(huán)隊(duì)列的每一個(gè)tick之后的一個(gè)隊(duì)列. 在事件循環(huán)的每個(gè)tick中, 可能出現(xiàn)的Promise.then(), MutationObserver異步任務(wù)不會(huì)導(dǎo)致一個(gè)新的事件添加到事件循環(huán)隊(duì)列末尾, 而是在當(dāng)前tick的任務(wù)隊(duì)列末尾再添加一個(gè)任務(wù).

  • console.log(1)  // 同步代碼,按順序調(diào)用
    setTimeout(()=>{
        console.log(2)  // 它被加入到事件循環(huán)隊(duì)列中,在下一個(gè)tick中調(diào)用
    },0)
    new Promise(function(resolve,reject){
        console.log(3) // new Promise不是異步代碼, Promise.then()才是異步代碼
        resolve()
    }).then(function(){
        console.log(4) // 它被加入到任務(wù)隊(duì)列, 本次tick的末尾
    })
    console.log(5) // 同步代碼,按順序調(diào)用
    // 打印 1 3 5 4 2
    
  • 不完善的補(bǔ)充: 一些文檔中將Promise.then與MutationObserver列為微任務(wù)隊(duì)列, 其它的異步代碼列為宏任務(wù)隊(duì)列. 個(gè)人理解為: 在每一個(gè)tick中, 微任務(wù)隊(duì)列會(huì)在同步代碼執(zhí)行后再執(zhí)行. 宏任務(wù)隊(duì)列丟進(jìn)eventLoop中等待調(diào)用.

  •         setTimeout(() => {
                console.log(1) // tick1
                setTimeout(() => {
                    console.log(2) // tick3
                    setTimeout(() => {
                        console.log(3) // tick5
                    }, 0);
                }, 0);
                setTimeout(() => {
                    console.log(4) // tick4
                }, 0);
            }, 0);
    
            setTimeout(() => {
                console.log(5) // tick2
            }, 0);
    // 打印1 5 2 4 3
    
?著作權(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)容