JS 事件循環機制 - 任務隊列、web API、JS主線程的相互協同

1、JS單線程、異步、同步概念

  從上一篇說明vue nextTick的文章中,屢次出現「事件循環」這個名詞,簡單說明了事件循環的步驟,以便理解nextTick的運行時機,這篇文章將更爲詳細的分析下事件循環。在此以前須要瞭解JS單線程,及由此產生的同步執行環境和異步執行環境。
  衆所周知,JS是單線程(若是一個線程刪DOM,一個線程增DOM,瀏覽器傻逼了~因此只能單着了),雖然有webworker醬紫的多線程出現,但也是在主線程的控制下。webworker僅僅能進行計算任務,不能操做DOM,因此本質上仍是單線程。
  單線程即任務是串行的,後一個任務須要等待前一個任務的執行,這就可能出現長時間的等待。但因爲相似ajax網絡請求、setTimeout時間延遲、DOM事件的用戶交互等,這些任務並不消耗 CPU,是一種空等,資源浪費,所以出現了異步。經過將任務交給相應的異步模塊去處理,主線程的效率大大提高,能夠並行的去處理其餘的操做。當異步處理完成,主線程空閒時,主線程讀取相應的callback,進行後續的操做,最大程度的利用CPU。此時出現了同步執行和異步執行的概念,同步執行是主線程按照順序,串行執行任務;異步執行就是cpu跳過等待,先處理後續的任務(CPU與網絡模塊、timer等並行進行任務)。由此產生了任務隊列與事件循環,來協調主線程與異步模塊之間的工做。
 

2、事件循環機制

                                  事件循環示例圖
 
  如上圖爲事件循環示例圖(或JS運行機制圖),流程以下:
    step1:主線程讀取JS代碼,此時爲同步環境,造成相應的堆和執行棧;
    step2:  主線程遇到異步任務,指給對應的異步進程進行處理(WEB API);
    step3:  異步進程處理完畢(Ajax返回、DOM事件處罰、Timer到等),將相應的異步任務推入任務隊列;
    step4: 主線程執行完畢,查詢任務隊列,若是存在任務,則取出一個任務推入主線程處理(先進先出);
    step5: 重複執行step二、三、4;稱爲事件循環。
  執行的大意:
    同步環境執行(step1) -> 事件循環1(step4) -> 事件循環2(step4的重複)…
  其中的異步進程有:
    a、相似onclick等,由瀏覽器內核的DOM binding模塊處理,事件觸發時,回調函數添加到任務隊列中;
    b、setTimeout等,由瀏覽器內核的Timer模塊處理,時間到達時,回調函數添加到任務隊列中;
    c、Ajax,由瀏覽器內核的Network模塊處理,網絡請求返回後,添加到任務隊列中。
 

3、任務隊列

  如上示意圖,任務隊列存在多個,同一任務隊列內,按隊列順序被主線程取走;不一樣任務隊列之間,存在着優先級,優先級高的優先獲取(如用戶I/O);
   3.一、任務隊列的類型
    任務隊列存在兩種類型,一種爲microtask queue,另外一種爲macrotask queue。
    圖中所列出的任務隊列均爲macrotask queue,而ES6 的 promise[ECMAScript標準]產生的任務隊列爲microtask queue。
            
   3.二、二者的區別
    microtask queue:惟一,整個事件循環當中,僅存在一個;執行爲同步,同一個事件循環中的microtask會按隊列順序,串行執行完畢;
    macrotask queue:不惟一,存在必定的優先級(用戶I/O部分優先級更高);異步執行,同一事件循環中,只執行一個。
 
   3.三、更完整的事件循環流程    
    將microtask加入到JS運行機制流程中,則:
      step一、二、3同上,
      step4:主線程查詢任務隊列,執行microtask queue,將其按序執行,所有執行完畢;
      step5:主線程查詢任務隊列,執行macrotask queue,取隊首任務執行,執行完畢;
      step6:重複step四、step5。
    microtask queue中的全部callback處在同一個事件循環中,而macrotask queue中的callback有本身的事件循環。
    簡而言之:同步環境執行 -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個) -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個)...
    利用microtask queue能夠造成一個同步執行的環境,但若是Microtask queue太長,將致使Macrotask任務長時間執行不了,最終致使用戶I/O無響應等,因此使用需慎重。
 

4、示例、驗證  

            console.log('1, time = ' + new Date().toString())
            setTimeout(macroCallback, 0);
            new Promise(function(resolve, reject) {
                console.log('2, time = ' + new Date().toString())
                resolve();
                console.log('3, time = ' + new Date().toString())
            }).then(microCallback);

            function macroCallback() {
                console.log('4, time = ' + new Date().toString())
            } 

            function microCallback() {
                console.log('5, time = ' + new Date().toString())
            }     

  結合第二節與第三節的分析,此處的執行流程應爲:vue

    同步環境:1 -> 2 -> 3web

    事件循環1(microCallback):5ajax

    事件循環2(macroCallback):4promise

  運行結果以下:瀏覽器

    

  運行結果與預期一致,驗證了在不一樣類型的任務隊列中,microtask queue中的callball將優先執行。網絡

    總結:由此咱們瞭解事件循環的機制,同時瞭解了任務隊列、JS主線程、異步操做之間的相互協做;同時認識了兩種任務隊列:macrotask queue、microtask queue,它們由不一樣的標準制定,microtask queue對應ECMAScript的promise屬性(ES6)和 DOM3的MutationObserver,文中說明了二者在事件循環中的運行狀況及區別;在從此的異步操做中,經過靈活運用不一樣的任務隊列,提高用戶交互性能,給出更加的響應和視覺體驗;同時,經過JS的事件循環機制,能夠更清楚JS代碼的執行流,從而更好的控制代碼,更有效、更好的爲業務服務。 
相關文章
相關標籤/搜索