JavaScript基礎四:事件循環EventLoop

什麼是事件循環?

JavaScript是一門單線程語言,即當有一個任務在執行的時候,其餘任務須要在後面等待。但在實際場景中,有些任務能夠放在比較靠後的位置,好比加載網絡資源時發送的異步請求。網絡

事件循環就是JavaScript異步執行機制的一種實現方式異步

JavaScript執行機制

前面說到執行上下文存在建立階段和執行階段,這一章節主要針對執行階段來說。即在分析JavaScript代碼執行時,首先要作一次執行上下文建立階段的分析,然後才能利用這篇文章的知識點來分析JavaScript的執行機制函數

咱們常說JavaScript是按照代碼順序一行行執行下來的,但這段代碼實際輸出的順序是2,1。這是爲何呢?post

setTimeout(function(){
 console.log(1);
},0)
console.log(2);

JavaScript引擎會智能地將任務分紅同步任務和異步任務。spa

  • 同步任務:主代碼
  • 異步任務:setTimeout、Promise、setInterval、nextTick等等

首先JavaScript引擎會將總體script代碼放入執行棧中,遇到同步任務則直接進入主線程執行,遇到異步任務時註冊回調函數,並將異步任務放進事件隊列中等待。線程

待主線程中的全部同步任務執行完畢後,纔會去事件隊列處取任務。根據隊列先進先出的特色,最早進入事件隊列的異步任務會被率先執行。3d

事件執行機制.jpg

而在實際場景中,並非從事件隊列一一取出異步任務直接執行這麼簡單。從宏觀定義上,咱們將JavaScript的任務分爲同步任務和異步任務,但在事件循環中,更加精細的分法是將任務分爲宏任務和微任務。不一樣類型的任務會進入不一樣的事件隊列中。code

  • 宏任務:通常的JavaScript同步代碼,定時器相關的異步代碼setTimeout、setInterval
  • 微任務:Promise、nextTick等

JavaScript引擎執行代碼過程

在JavaScript引擎執行代碼的過程當中,首先將總體代碼做爲一個宏任務執行,遇到通常的同步代碼當即執行,遇到定時器相關如setTimeout這種宏任務代碼則先將其放入到宏任務事件隊列中,遇到Promise這種則將其catch、then函數放入到微任務隊列中。blog

總體代碼做爲第一次宏任務執行完畢後,JavaScript引擎會先到微任務隊列中看看有沒有任務,若有任務則將其所有執行,若是沒有則進入到下一次宏任務中。也就是說JavaScript執行機制是按照宏任務-微任務-宏任務-微任務...這樣子循環執行的。隊列

根據隊列先進先出的特色,取出宏任務事件隊列中第一個宏任務率先執行,遇到同步代碼當即執行,遇到微任務則繼續進入微任務事件隊列。待這一次的宏任務執行完畢後,則繼續執行全部的微任務。如此循環下去,則就是事件循環。

宏任務與微任務.jpg

先舉個簡單的例子

setTimeout(function(){
  console.log(0);
 },3000);
 new Promise((resolve,reject)=>{
   console.log(2);
   resolve();
 }).then(()=>{
     console.log('resolve');
  }
 );
 console.log(1)

第一遍事件循環過程:

  • 依據上述的結論,總體代碼做爲宏任務首先進入主線程中執行,setTimeout是一個宏任務,則先將其放入到宏任務事件隊列中。
  • 繼續下面的代碼,遇到Promise裏面的當即執行語句,則直接輸出2。遇到then函數將其放入到微任務事件隊列中,遇到最後一句輸出1。
  • 在這一遍宏任務執行過程當中,宏任務事件隊列中有一個setTimeout在等待執行,微任務事件隊列中有一個then函數在等待執行。
  • 則依據宏任務-微任務-宏任務-微任務的執行特色,接下來要去微任務事件隊列中取任務,則跟着要輸出then函數中的resolve。微任務事件隊列僅有這一個微任務,至此微任務所有執行完畢。第一遍事件循環過程結束。

第二遍事件循環過程:

  • 第二遍事件循環仍舊從宏任務開始,要去宏任務事件隊列中取一個宏任務執行,此時第一個宏任務是setTimeout,則直接執行輸出0便可。
  • 再去微任務事件隊列中查看是否還有微任務等待執行,發現微任務事件隊列中沒有任務。則跳過,繼續去宏任務事件隊列中查看是否有宏任務,依舊沒有任務。至此事件循環結束。這段代碼輸出的順序是2,1,resolve,0。

上面給定時器的延遲時間是3秒鐘,有無多是時間片競爭的機制搶着輸出?試着把setTimeout的延遲時間改成0。

setTimeout(function(){
 console.log(0);
},0);
new Promise((resolve,reject)=>{
  console.log(2);
  resolve();
}).then(()=>{
    console.log('resolve');
 }
);
console.log(1)

發現輸出的順序一致,實際上將延遲設置爲0,也不是當即執行。

依據上面的結論,將setTimeout的延遲時間設爲0的意思是待主線程空閒後即可以執行,還得等主線程按照宏任務-微任務-宏任務-微任務的事件循環順序執行完畢後。實際上即便主線程空閒了,延遲執行也不會達到0秒當即執行,最低也是4ms。

小結

  1. 同步任務進入主線程率先執行,異步任務註冊回調函數後進入事件隊列
  2. 宏任務:大部分JavaScript同步代碼,定時器相關setTimeoutsetInterval
    微任務:PromisenextTick
  3. JavaScript執行機制按照宏任務-微任務-宏任務-微任務的循環順序執行。注意在每輪事件循環過程當中,執行宏任務是從宏任務事件隊列中取出最先進入的一個任務,而微任務則是所有執行完畢。
參考文章: https://juejin.im/post/59e85e...
相關文章
相關標籤/搜索