🔥「吊打面試官」完全理解事件循環、宏任務、微任務

歡迎你們來到"吊打面試官"系列,Blue在這個系列中,會和你們分享各類面試中的知識和「坑」,歡迎你們關注我,精彩內容不錯過,若是碰到感興趣的題目想討論,歡迎你們經過留言告訴我,謝謝,但請記住:javascript

面試或許能夠應付,但不要糊弄本身,完全掌握知識自己纔是提高的關鍵 ———— Blue說的前端

事件循環(Event Loop)、宏任務和微任務這幾個概念,是最近面試中常常問到的知識點,理解它不但能幫助咱們更好地應對面試,也能完全理解js的執行機制,寫出更爲高效的代碼,本文將幫你完全理解事件循環、宏任務、微任務,歡迎點贊、收藏、評論、轉發java

內容大綱web

  • 什麼是事件循環、JS代碼的內在執行機制
  • 什麼是宏任務和微任務
  • async的坑
  • 總結、更新&補充

什麼是事件循環?

在JS中咱們常常會須要「同時」進行多項工做,例如:定時器、事件、異步數據交互等,那麼JS是如何管理這些任務的,又是如何肯定他們的執行順序的?面試

首先,全部的語言都擁有併發模型的概念,也就是說多個任務如何同時執行,大部分語言支持多線程執行,JS擁有全部語言中最簡單的併發模型——JS使用單線程的"事件循環(Event Loop)"來處理多個任務的執行編程

咱們用示意性的代碼,來表示js的事件循環數組

while(獲取任務()){
  執行任務();
}
複製代碼

簡單來講,js的事件循環,每次讀取一個任務,而後執行這個任務,執行完再繼續獲取下一個,若是暫時沒有任務,就暫停執行,等待下一個任務到來;若是在執行任務的過程當中有新的任務到達,也不會中斷現有任務的執行,而是添加到隊列的尾部等待promise

1-事件循環

結論是,JS使用基於事件循環的單線程執行方式,並且是非搶斷執行的(也就是說,不管發生什麼,都會把當前任務執行完,不會出現執行到一半就去執行別的任務的狀況),你們可能會奇怪,這樣作不是性能很低嗎?確實,我們來看看這樣作的優缺點瀏覽器

多線程(C、Java等語言) 單線程事件驅動(JavaScript)
複雜性 複雜度高,須要面對線程間同步等大量消耗頭髮的問題 簡單易於使用,永遠不會出現資源爭搶的問題
性能 CPU性能很高,適合計算密集型任務 單一線程,沒法發揮CPU的極限性能(可經過webWorker補充),不過前端應用本就不是計算密集型的
阻塞 不會阻塞,大型任務能夠單開線程處理 其實也不會阻塞,由於JS中的IO任務都是異步的(文件、網絡),雖然大型計算任務依然會阻塞UI線程,但這種狀況對前端其實很少

因此,JS的單線程事件循環其實很適合前端使用,大幅的簡化了程序的複雜度,同時前端少會有大型計算任務,因此性能也並不是問題markdown

結論:單線程事件循環看着好像"有點low",但其實很是適合前端開發

任務隊列的概念

理解了事件循環的概念,咱們來繼續看看任務隊列,所謂任務隊列,其實就是保存待處理任務的一個數組

2-任務隊列

每當咱們要執行一個新的任務(例如:定時器),咱們就會在隊列尾部添加一個task,等到當前任務完成,事件循環會去隊列頭部尋找下一個可執行任務,咱們用一個例子來更好的理解這一點

實例:任務隊列與定時器

console.log('aaaa');

setTimeout(()=>{
  console.log('cccc');
}, 0);  //這個0毫秒是重點

console.log('bbbb');
複製代碼

3-執行結果

用咱們上面說的任務隊列的思想,來分析這個程序執行的過程:

  • 第1步:它會先執行console.log('aaaa'),很普通的同步代碼
  • 第2步:定時器是這個問題的關鍵,尤爲是0毫秒的
    • 0毫秒意味着沒有延遲,因此本應直接執行console.log('cccc'),可是...
    • 定時器不會馬上執行,由於它不能打斷當前任務(JS是非搶斷執行),因此它只能被放到隊列的尾部
  • 第3步:執行console.log('bbb')
  • 第4步:當前任務已經執行完成了,這時纔會從任務隊列中尋找下一個任務(也就是以前放入的定時器任務)
  • 第5步:執行定時器任務,也就是console.log('cccc')

說了這麼多,看圖更容易理解

3-0

3-1

3-2

3-3

3-4

總結一下:

  • 正在執行的任務(好比上面的console.log aaa和bbb)永遠不會被打斷,全部異步代碼會被添加到隊列等待執行
  • 定時器不論時間多短(哪怕是0)也不會馬上執行,而是被放到任務隊列尾部

題外話:定時器爲何老是不許?

你們必定注意過一個事情,那就是JS中的定時器常常不許(其實全部語言都這樣),這個問題也跟上面的任務隊列有關

  • 定時器的時間,並非函數執行的時間,而是"最短x毫秒後,將任務添加到隊列中"
  • 也就是說,除非隊列徹底是空的,不然定時器的時間到了,它僅僅是開始排隊罷了
  • 那麼,隊列有可能永遠是空的嗎?顯然不可能,就算沒有你的任務,瀏覽器也有不少它的工做——渲染、重排、清理內存什麼的

因此結論就是,由於有其餘任務在排隊,定時器永遠不可能徹底準時

實例:更多定時器、更多的任務

上面的東西你們都沒問題了的話,再給你們一個例子,檢測一下本身的學習成果

console.log('aaa');

setTimeout(() => console.log(111), 0);
setTimeout(() => console.log(222), 0);

console.log('bbb');
複製代碼

4-執行結果

相信這個也難不倒你們了,簡單來講,setTimeout即便是0毫秒,也不會當即執行,而是堆在隊列尾部等待,而當前任務不會被打斷,因此aaa和bbb先出來,而後再從隊列尾部拿出一個任務,也就是111,而後再拿一個222

4-1

4-2

4-3

4-4

4-5

4-6

任務生而平等,直到有人衝了VIP

上面咱們講了關於事件循環和任務隊列的問題,那麼接下來blue要告訴你們一個驚人的事情(有啥好驚人的...)

宏任務?微任務?

其實js裏任務隊列不僅有一條,而是有兩條,並且有一條仍是SVIP年費白金隊列

  • 宏任務(macroTask或簡稱Task):普通的任務,正常執行
  • 微任務(microTask):SVIP年費白金會員任務,優先於宏任務執行(但依然是非搶斷的)

5-帶微任務的事件循環

在考慮微任務的狀況下,JS的事件循環是按照這樣的順序執行:

  • 獲取下一個任務,若是沒有就進入等待
  • 當執行完一個任務以後,會執行全部排隊的微任務
  • 而後再次獲取下一個任務
while(獲取任務()){
  執行任務();
  
  微任務隊列.forEach(微任務=>{
    執行微任務();
  });
}
複製代碼

因此,微任務其實比普通任務的優先級更高,由於在一個任務結束後,事件循環會找到並執行所有微任務,而後再繼續查找其餘任務,但這時候咱們會有兩個問題:

  • 哪些任務屬於宏任務,哪些任務屬於微任務?
  • 宏任務有啥特殊的,爲何它有這麼大特權?

哪些操做屬於宏任務?哪些屬於微任務?

最先的js只有宏任務,而微任務是後來才加的

  • 宏任務:正常的異步任務都是宏任務,最多見的就是定時器(setInterval, setImmediate, setTimeout)、IO任務

  • 微任務:微任務出現比較晚,queueMicrotask、Promise和async屬於微任務(固然,async就是promise)

說了這麼多,來看個例子吧,瞬間幫你搞清楚

console.log('aaa');

setTimeout(() => console.log(111), 0); //異步任務
queueMicrotask(() => console.log(222)); //異步任務

console.log('bbb');
複製代碼

6-執行結果

Blue帶你來看一下,這個東西的執行過程

  • 第1步,毫無疑問的,確定是先執行aaa,這個沒任何疑問
  • 第2步,定時器確定不是馬上執行,因此111去排隊了,可是注意,定時器是宏任務
  • 第3步,queueMicrotask也不會當即執行,因此222也去排隊了,但Promise進的是VIP隊列
  • 第4步,執行到bbb了,並且當前任務就結束了,接下來是重點
  • 第5步,再次查詢任務以前(也就是那個while),會先完成全部微任務,因此此時,222獲得優先執行,畢竟是VIP嘛
  • 第6步,完成全部微任務後(也就是第5步),會尋找下一個任務,這時111的那個定時器才獲得執行

因此,整個執行過程是aaa,bbb,111,222,如今咱們也明白了微任務是什麼,其實微任務就是獲得優先執行的異步任務

爲何要有微任務?

按照官方的設想,任務之間是不平等的,有些任務對用戶體驗影響大,就應該優先執行,而有些任務屬於背景任務(好比定時器),晚點執行沒有什麼問題,因此設計了這種優先級隊列的方式

注意:async的坑

上面咱們說到Promise也是微任務,並且async就是promise的一種語法包裝(所謂語法糖),那async是否是必定是按照微任務的方式執行呢?"不全是"

來吧,直接上個例子,在你們蒙圈以前撈一下

console.log('aaa');

(async ()=>{
  console.log(111);  //在async裏面
})().then(()=>{
  console.log(222);  //在async的then裏面
});

console.log('bbb');
複製代碼

相信我不說你們也能看出來,這個程序的坑就在111222這裏,換句話說,async究竟是怎麼個異步法?

先上結果,再說緣由

7-執行結果

  • 第1步,依然是毫無懸念的aaa,過
  • 第2步,雖然async是異步操做,但async函數自己(也就是111所在的()=>{}),其實依然是同步執行的,除非有await出現,這個下面會說,因此,這裏111直接同步執行,而不是放到隊列裏等待
  • 第3步,重點來了,then不會同步執行,它纔是異步的,並且是一個微任務,因此222不會當即執行,而是排到隊列尾部
  • 第4步,執行bbb沒什麼好說的,並且當前任務也就執行完成了
  • 第5步,最後從任務隊列中把排隊的222拿出來,完成整個程序

是否是很好懂?那麼,再來看看await的做用吧,await實際上是異步的,跟then差很少(從語法上來講,await其實就是promise的then),直接上例子

console.log('aaa');

(async ()=>{
  console.log(111);
  await console.log(222);
  console.log(333);
})().then(()=>{
  console.log(444);
});

console.log('ddd');
複製代碼

8-執行結果

咱們來看看這個東西怎麼執行的:

  • 第1步、aaa不說了
  • 第2步、111是同步執行的,上面說過
  • 第3步、222這裏很重要了,首先,console.log本身是同步的,因此當即就會執行,咱們能直接看到222,可是await自己就是then,因此console.log(333)沒法直接執行,而是老老實實去排隊,並且,由於整個async並未執行完,它的then(也就是444)沒法觸發
  • 第4步、ddd應該也不用說,當前任務到這裏執行完畢
  • 第5步、從任務隊列中把333拉出來,而且執行了,這時整個async纔算完成,因此把then推到隊列中等待執行
  • 第6步、把console.log(444)拉出來執行,看到444

因此,一個結論是,await其實等價於then(事實上他倆也確實是一個東西),都是將後續任務放到微任務隊列中等待,而不會當即執行

再來鞏固一下吧

都清楚了吧?那Blue再帶你看個例子鞏固一下吧

console.log('aaa');

setTimeout(()=>console.log('t1'), 0);
(async ()=>{
  console.log(111);
  await console.log(222);
  console.log(333);

  setTimeout(()=>console.log('t2'), 0);
})().then(()=>{
  console.log(444);
});

console.log('bbb');
複製代碼

9-執行結果

首先,這個例子的坑很是多,不過這個例子搞定了,這課你就算畢業了,加油

  • 第1步、毫無懸念aaa,過
  • 第2步、t1會放入任務隊列等待
  • 第3步、111會直接執行,由於async自己不是異步的(上面有說)
  • 第4步、222也會直接執行,可是接下來的console.log(333);setTimeout(()=>console.log('t2'), 0);就塞到微任務隊列裏等待了
  • 第5步、bbb毫無疑問,並且當前任務完成,優先執行微任務隊列,也就是console.log(333)開始的那裏
  • 第6步、執行333,而後定時器t2會加入任務隊列等待(此時的任務隊列裏有t1和t2兩個了),而且async完成,因此console.log(444)進入微任務隊列等待
  • 第7步、優先執行微任務,也就是444,此時全部微任務都完成了
  • 第8步、執行剩下的普通任務隊列,這時t1t2纔會出來

總結

是時候梳理一遍Blue講過的東西了,那麼首先

15-三連

  • 事件循環:JS採用單線程的事件循環方式管理異步任務,優勢是簡化編程模型,缺點是沒法發揮CPU的所有性能(但對前端其實沒影響)
  • 任務隊列:JS採用非搶斷式運行,當前任務不會被打斷,有新的異步任務時,會放入任務隊列
  • 宏任務、微任務:宏任務就是普通異步任務,是最先出現的,微任務更關乎用戶體驗,因此獲得優先執行
  • 常見宏任務:定時器、IO任務
  • 常見微任務:queueMicrotask、await、then

5-有bug?想補充?

感謝你們觀看這篇教程,有任何問題或想和我交流,請直接留言,發現文章有任何不妥之處,也請指出,提早感謝

相關文章
相關標籤/搜索