什麼是EventLoop?

  咱們知道js的最大特色就是單線程,就是同一時間只能作一件事。即使HTML5提出Web Worker容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。javascript

一、任務隊列

  由於js的單線程的特性,全部運行在js線程中的代碼須要根據某種規則來按隊列執行。由於js中既有同步事件,又有異步事件,因此有時候咱們在工做中寫js代碼時總會一臉懵逼,特別是在瀏覽器環境中進行斷點調試時,因此弄懂js代碼的執行順序很是重要。

   按照js回調事件的特性,將任務分爲同步任務和異步任務。html

  • 一、全部同步任務都在主線程中執行,並造成一個執行棧,異步任務在有了運行結果以後纔會將回調函數添加到任務隊列中。
  • 二、代碼運行時會先去執行主線程中的代碼,等到主線程中的代碼執行完畢後,纔會去讀取任務隊列中的回調函數並放置到執行棧上來執行,先添加到任務隊列中的會被先執行。等到任務隊列中的回調函數執行完畢後,又會回到主線程上來。
  • 三、主線程中會不斷重複執行第二步,並造成一個循環。這就是所謂的EventLoop。

二、事件隊列

  任務隊列按照某種條件又能夠細分爲microtask 和 macrotask,一般咱們會稱之爲微任務和宏任務。代碼執行的優先級爲:主線程>微任務>宏任務。java

  • 微任務主要有:ES6的Promise(then函數),node.js中的process.nextTick(),MessageChannel(消息通道,相似worker)
  • 宏任務主要有:總體代碼script、setTimeout 和 setInterval、還有 MessageChannel,setImmediate, I/O

  js中代碼執行的順序是:首先執行主線程中的代碼(宏任務),開始第一次循環,執行完畢後,再執行全部的微任務,而後再執行宏任務,看該宏任務中是否有微任務,若是有的話,就將全部的微任務執行完畢。再執行新的宏任務,如此不斷的反覆執行下去。
事件執行機制(即js代碼執行機制)以下圖(圖片來自ssssyoki)所示。node

詳情請看以下代碼

//宏任務
        setTimeout(function(){
            console.log("我是宏任務1");
        })
        let p=new Promise((resolve,reject)=>{
            console.log("promise1");
            resolve();
        });
        //微任務
        p.then(res=>{
            console.log("我是微任務1")
        });
        let p2=new Promise((resolve,reject)=>{
            console.log("promise2");
            resolve();
        });
        //微任務
        p2.then(res=>{
            console.log("我是微任務2")
        });
        //宏任務
        setTimeout(function(){
            console.log("我是宏任務2");
        })
        console.log("我是主線程");
      
複製代碼

執行結果以下所示:promise

  代碼解析:瀏覽器

  • 一、js代碼開始執行後,遇到setTimeout,將其回調函數添加到宏任務隊列中,記爲setTimeout1;
  • 二、遇到Promise,Promise構造函數中的代碼是同步任務,因此最早輸出promise1,同時將then函數添加到微任務隊列中,記爲then1;
  • 三、接着再次遇到Promise,而後輸出promise2,並將其then函數添加在微任務隊列中,記爲then2。
  • 四、執行到setTimeout,將其回調函數添加到宏任務隊列中,記爲setTimeout2。
  • 五、最後輸出"我是主線程"。此時主線程中的代碼執行完畢。此時任務隊列中的狀況以下。
宏任務 微任務
setTimeout1 then1
setTimeout2 then2
  • 六、接着開始執行微任務隊列中的全部任務,輸出"我是微任務1","我是微任務2",此時第一輪事件事件循環結束。
  • 七、隨着setTimeout1回調事件的執行,意味着第二輪事件循環的開始,接着輸出"我是宏任務1",它的結束意味着第二輪事件循環的結束。
  • 八、隨着setTimeout2回調事件的執行,意味着第三輪事件循環的開始,它的結束就意味着第三輪事件循環的結束。
  • 九、開始第二次循環,執行第一個宏任務中的代碼,並輸出"我是宏任務1",接着輸出"我是宏任務2"。 因此個人理解其實就是宏任務隊列中有幾個宏任務,就意味着在這次js代碼執行過程當中有幾回事件循環。

三、瀏覽器環境中和node環境中EventLoop的異同

  示例代碼以下所示;
//宏任務,第二次事件循環的開始
        setTimeout(function() {
            console.log('setTimeout1');
            new Promise(function(resolve) {
                console.log('setTimeout1-Promise');
                resolve();
                //微任務
            }).then(function() {
                console.log('setTimeout1-then')
            })
        })
        new Promise(function(resolve) {
            console.log('Promise');
            resolve();
            //微任務
        }).then(function() {
            console.log('then')
        })
        //宏任務,第三次事件循環的開始
        setTimeout(function() {
            console.log('setTimeout2');
            new Promise(function(resolve) {
                console.log('setTimeout2-promise');
                resolve();
            }).then(function() {
                console.log('setTimeout2-then')
            })
        })
複製代碼

一、瀏覽器中js事件循環機制

  上面代碼在瀏覽器環境中的輸出結果以下所示:網絡

  因此我得出結論:在瀏覽器環境下js代碼的執行順序是。主線程>微任務隊列中全部的回調函數>宏任務隊列中的全部宏任務。主線程和微任務成爲第一次事件循環。宏任務隊列中的一個宏任務就是一個事件循環。

二、node環境中js事件循環機制

  我的以爲node中的js執行機制比在瀏覽器中的要複雜一些。盜用掘金網友的一張圖很好的解釋了node環境中的事件循環機制。

  node環境中的eventLoop是按階段來執行的,主要有6個階段,這個階段裏的代碼執行完畢,纔會去執行下一個階段裏的代碼。6個階段中的代碼都執行完畢纔算是完成一個事件循環。

  • Node的Event Loop分階段,階段有前後,依次是
       一、expired timers and intervals,即到期的setTimeout/setInterval
       二、I/O events,包含文件,網絡等等
       三、immediates,經過setImmediate註冊的函數
       四、close handlers,close事件的回調,好比TCP鏈接斷開
  • 同步任務及每一個階段以後都會清空microtask隊列
       一、優先清空next tick queue,即經過process.nextTick註冊的函數
       二、再清空other queue,常見的如Promise
  • 而和規範的區別,在於node會清空當前所處階段的隊列,即執行全部task

  而在node環境中的輸出結果是這樣的,兩次執行結果還不同異步

  上面的代碼在node環境中的執行結果以下所示。 函數

結果分析:oop

  • 首先執行主線程中的代碼,輸出promise1。
  • 接着清空微隊列中的任務,即執行外層then函數中的代碼,輸出then1(微任務不在任何一個階段執行,在各個階段切換的中間執行)。
  • 接着開始執行timer階段中的全部任務,因此此時輸出setTimeOut1——>setTimeOut1-Promise——>setTimeOut2——>setTimeOut2-Promise。
  • timer階段中的任務執行完畢後,又開始執行微任務隊列中的全部任務。因此此時輸出setTimeOut1-then、setTimeOut1-then2。

最後: node中的eventLoop運行機制比較複雜,因此還須要花費更多的時間去多多研究。

參考文檔
一、這一次,完全弄懂 JavaScript 執行機制
二、JavaScript 運行機制詳解:再談Event Loop
三、Eventloop不可怕,可怕的是趕上Promise
四、瀏覽器說:雖然都叫event loop,可是我和node不同

相關文章
相關標籤/搜索