事件循環 EventLoop(Promise,setTimeOut,async/await執行順序)

什麼是事件循環?想要了解什麼是事件循環就要從js的工做原理開始提及:html

JS主要的特色就是單線程,所謂單線程就是進程中只有一個線程在運行。html5

爲何JS是單線程的而不是多線程的呢?promise

JS的主要用途就是與用戶交互,操做DOM,假設JS同時有兩個線程,一個線程中在某個DOM節點上添加或者修改內容,而另外一個線程在這個DOM節點上執行刪除該節點操做,這樣就會產生衝突。多線程

單線程就意味着全部任務都須要排隊,前一任務結束,纔會執行後一個任務,當是若是當遇到前一個任務耗時很長的狀況,後一個任務就不得不一直等着。所以,就有了同步任務、異步任務。異步

同步任務和異步任務在js中是如何執行的呢?async

js的代碼運行會造成一個主線程和一個任務隊列。主線程會自上而下依次執行咱們的js代碼,造成一個執行棧。函數

同步任務就會被放到這個主線程中依次執行。而異步任務被放入到任務隊列中執行,執行完就會在任務隊列中打一個標記,造成一個對應的事件。當主線程中的任務所有運行完畢,js會去提取並執行任務隊列中的事件。這個過程是循環進行的,這就是EventLoop。oop

JS引擎執行異步代碼不用等待,是由於有事件隊列和事件循環。線程

事件循環是指主線程重複從事件隊列中取消息、執行的過程。指整個執行流程。code

事件隊列是一個存儲着待執行任務的序列,其中的任務嚴格按照時間前後順序執行,排在隊頭的任務會率先執行,而排在隊尾的任務會最後執行。(即先進先出)

事件隊列:

  • 一個線程中,事件循環是惟一的,可是任務隊列能夠有多個;
  • 任務隊列又分macro-task(宏任務)和micro-task(微任務);
  • macro-task包括:script(總體代碼)、setTimeout、setInterval、setImmediate、I/O、UI rendering;
  • micro-task包括:process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)
  • setTimeout/Promise等稱爲任務源,而進入任務隊列的是他們制定的具體執行任務;來自不一樣任務源的任務會進入到不一樣的任務隊列,其中setTimeout與setInterval是同源的。
 

事件循環運行機制

(1)執行一個宏任務(棧中沒有就從事件隊列中獲取)

(2)執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中;

(3)宏任務執行完畢後,當即執行當前微任務隊列的全部微任務;

(4)當前微任務執行完畢,開始檢查渲染,而後GUI線程接管渲染;

(5)渲染完畢後,JS線程繼續接管,開始下一個宏任務。

 

 

事例:

                 async function async1() {           
                     console.log("async1 start");  //(2)        
                     await  async2();            
                     console.log("async1 end");   //(6)    
                 }        
                 async  function async2() {          
                     console.log( 'async2');   //(3)     
                 }       
                 console.log("script start");  //(1)      
                 setTimeout(function () {            
                     console.log("settimeout");  //(8)      
                 },0);        
                 async1();        
                 new Promise(function (resolve) {           
                     console.log("promise1");   //(4)         
                     resolve();        
                 }).then(function () {            
                     console.log("promise2");    //(7)    
                 });        
                 console.log('script end');//(5)

 

按照事件循環機制分析以上代碼運行流程:

  1. 首先,事件循環從宏任務(macrotask)隊列開始,首先讀取script(總體代碼)任務;當遇到任務源(task source)時,則會先分發任務到對應的任務隊列中去。

  2. 而後咱們看到首先定義了兩個async函數,此時沒有調用,接着往下看,而後遇到了 `console` 語句,直接輸出 `script start`。輸出以後,script 任務繼續往下執行,遇到 `setTimeout`,其做爲一個宏任務源,則會先將其任務分發到對應的任務隊列中。

  3. script 任務繼續往下執行,執行了async1()函數,async函數中在await以前的代碼是當即執行的,因此會當即輸出`async1 start`。
遇到了await時,會將await後面的表達式執行一遍,因此就緊接着輸出`async2`,而後將await後面的代碼也就是`console.log('async1 end')`加入到microtask中的Promise隊列中,接着跳出async1函數來執行後面的代碼。

  4. script任務繼續往下執行,遇到Promise實例。因爲Promise中的函數是當即執行的,然後續的 `.then` 則會被分發到 microtask 的 `Promise` 隊列中去。因此會先輸出 `promise1`,而後執行 `resolve`,將 `promise2` 分配到對應隊列。

  5. script任務繼續往下執行,輸出了 `script end`,至此,全局任務就執行完畢了。
根據上述,每次執行完一個宏任務以後,會去檢查是否存在 Microtasks;若是有,則執行 Microtasks 直至清空 Microtask Queue。
於是在script任務執行完畢以後,開始查找清空微任務隊列。此時,微任務中, `Promise` 隊列有的兩個任務`async1 end`和`promise2`,所以按事件隊列先進先出的原則,前後順序輸出 `async1 end,promise2`。當全部的 Microtasks 執行完畢以後,表示第一輪的循環就結束了。

  6. 第二輪循環依舊從宏任務隊列開始。此時宏任務中只有一個 `setTimeout`,取出直接輸出便可,至此整個流程結束。

相關文章
相關標籤/搜索