淺談Event Loop

前言

上一篇中@TDGarden說:又到了春暖花開、萬物復甦的季節,你們都忙着談戀愛,沒時間寫博客了。
而後在羣裏發了一張圖給我:html

說這個位置適合我。
因而我就懂了。我該寫博客了。畢竟咱們前端如今只有我有時間寫博客。

好了,不瞎扯了,咱們進入正題,來聊聊Event Loop。本文算是對這幾天來學習Event Loop的總結和梳理,參考了不少大佬的文章,若有錯誤,懇請指正。前端

從單線程提及

衆所周知,js是一種單線程語言。爲何是單線程呢?我引用一句爛大街的話:假設js同時有兩個線程,一個線程想要在某個dom節點上增長內容,另外一個線程想要刪除這個節點,這時要以哪一個爲準呢?固然,多線程有多線程的解決辦法,加鎖啊,可是這樣的話,又會引入鎖、狀態同步等問題。es6

js是瀏覽器腳本語言,主要用途是與用戶互動,操做dom,多線程會帶來很複雜的同步問題。web

好吧,那就單線程吧。可是單線程又帶來了單線程的問題,只有一個線程啊,任務要排隊執行,若是前一個任務執行時間很長(ajax請求後臺數據),後面的任務就都得等着。面試

Event Loop就出現了,來背單線程的鍋。ajax

Event Loop

往下看以前你應該知道棧、隊列、同步任務、異步任務、執行棧這些基本概念。api

關於執行棧有一篇很詳細的文章推薦:JavaScript深刻之執行上下文棧promise

請看下圖: 瀏覽器

  1. js在執行代碼時,代碼首先進入執行棧,代碼中可能包含一些同步任務和異步任務。bash

  2. 同步任務當即執行,執行完出棧,over。

  3. 異步任務也就是常見的ajax請求、setTimeout等,代碼調用到這些api的時候,WebAPIs來處理這些問題,執行棧繼續執行。

  4. 異步任務有了運行結果時,(當ajax請求結果返回時),WebAPIs把對應的回調函數放到任務隊列。

  5. 執行棧爲空時來讀取任務隊列中的第一個函數,壓入執行棧。

步驟5不斷重複,執行棧爲空時,系統就去任務隊列中拿第一個函數壓入棧繼續執行。這個過程不斷重複,這就是事件循環(Event Loop)。

來看一個簡單的demo。

console.log(1);

setTimeout(() => {
    console.log(2);
}, 2000);

console.log(3);
複製代碼
  • console.log(1) 同步任務,輸出1
  • setTimeout異步任務,交給webapis去處理,2s後,console.log(2)進入任務隊列
  • console.log(3)同步任務,輸出3
  • 執行棧爲空,系統讀取任務隊列裏的事件
  • 執行console,log(2),輸出2

宏任務&微任務

說到這兒固然還沒完。相信你確定見過process.nextTickpromise吧,這時候執行順序會有點兒複雜,往下看。

微任務、宏任務與Event-Loop用了很通俗的例子講了宏任務和微任務的區別,我這裏就不囉嗦了。若是你不想了解也不要緊,由於常見的宏任務、微任務就那幾種,記住就能夠了。

  • 常見的宏任務:script(總體代碼)setTimeoutsetIntervalI/OsetImmedidate
  • 常見的微任務:process.nextTickMutationObserverPromise.then catch finally

process.nextTicksetImmidate是隻支持Node環境的。

還有,process.nextTick是有一個插隊操做的,就是說他進入微任務隊列時,會插到除了process.nextTick 其餘的微任務前面。

因此,咱們上面提到的任務隊列,是包括一個宏任務隊列和一個微任務隊列的。每次執行棧爲空的時候,系統會優先處理微任務隊列,處理完微任務隊列裏的全部任務,再去處理宏任務。

作兩道題

前面叨叨了那麼多,下面作兩道題試試水吧。

new Promise(resolve => {
    resolve(1);
    Promise.resolve().then(() => console.log(2));
    console.log(4);
}).then(t => console.log(t));
console.log(3);
複製代碼

hahahhhhh我搬出了阮老師的題。

  • 首先new Promise執行,resolve(1)表示建立的promise對象的狀態變爲resolved
  • Promise.resolve()至關於建立了一個promise對象,then裏面的匿名回調函數進入微任務隊列,此時的微任務隊列是[() => console.log(2)]
  • 輸出 4
  • new Promise的then函數裏面的匿名回調進入微任務隊列, 此時的微任務隊列是[() => console.log(2), t => console.log(t)]
  • 輸出 3

因此,最後輸出的順序是4 3 2 1。

emmmmmmm若是你不懂,那我以爲你能夠先去複習一下promise

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
setTimeout(function() {
  console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
  console.log('promise1');
  resolve();
}).then(function() {
  console.log('promise2');
});
process.nextTick(() => {
  console.log('nextTick');
})
console.log('script end');
複製代碼

這是一道爛大街的面試題。。我不信你沒見過。話很少說,咱們來分析emmmmm我建議你本身先作一下,再往下看。

看到async/await沒必要緊張,語法糖而已。async表示函數裏有異步操做,await以前的代碼該怎麼執行怎麼執行,await右側表達式照常執行,後面的代碼被阻塞掉,等待await的返回。返回是非promise對象時,執行後面的代碼;返回promise對象時,等promise對象resolved時再執行。

因此能夠理解成後面的代碼放到了promise.then 裏面。

  • 輸出 script start
  • WebAPIs在0s(哦,好像最短是4ms)以後把setTimeout裏面的匿名回調函數丟進宏任務隊列,簡記爲['setTimeout'] (請記得丟進任務隊列裏的是回調函數,函數!)
  • 輸出async1 start
  • 輸出async2
  • 要輸出async1 end代碼被丟進微任務隊列,此時的微任務隊列爲['async1 end']
  • 輸出promise1
  • promise對象狀態變爲resolved
  • promise.then 裏的匿名函數進入微任務隊列,此時的微任務隊列爲['async1 end', 'promise2']
  • nextTick插隊到微任務隊列對首,['nextTick', 'async1 end', 'promise2']
  • 輸出script end
  • 執行棧空
  • 輸出nextTick
  • 輸出async1 end
  • 輸出promise2
  • 微任務隊列爲空
  • 輸出setTimeout

結語

若是看完本文你仍是沒太懂,那我建議你能夠多看幾篇文章,一個燒餅吃不飽,十個就差很少了。

涉及到promiseasync/awaitNode和瀏覽器環境下事件循環的區別等問題本文沒有細講,可是這些知識會幫你更好地掌握Event Loop。

看完promise能夠作一下題試試水:Eventloop不可怕,可怕的是趕上Promise

參考文章

Author:小夭yao愛吃糖糖糖

相關文章
相關標籤/搜索