歡迎你們來到"吊打面試官"系列,Blue在這個系列中,會和你們分享各類面試中的知識和「坑」,歡迎你們關注我,精彩內容不錯過,若是碰到感興趣的題目想討論,歡迎你們經過留言告訴我,謝謝,但請記住:javascript
面試或許能夠應付,但不要糊弄本身,完全掌握知識自己纔是提高的關鍵 ———— Blue說的前端
事件循環(Event Loop)、宏任務和微任務這幾個概念,是最近面試中常常問到的知識點,理解它不但能幫助咱們更好地應對面試,也能完全理解js的執行機制,寫出更爲高效的代碼,本文將幫你完全理解事件循環、宏任務、微任務,歡迎點贊、收藏、評論、轉發java
內容大綱web
在JS中咱們常常會須要「同時」進行多項工做,例如:定時器、事件、異步數據交互等,那麼JS是如何管理這些任務的,又是如何肯定他們的執行順序的?面試
首先,全部的語言都擁有併發模型的概念,也就是說多個任務如何同時執行,大部分語言支持多線程執行,JS擁有全部語言中最簡單的併發模型——JS使用單線程的"事件循環(Event Loop)"來處理多個任務的執行編程
咱們用示意性的代碼,來表示js的事件循環數組
while(獲取任務()){
執行任務();
}
複製代碼
簡單來講,js的事件循環,每次讀取一個任務,而後執行這個任務,執行完再繼續獲取下一個,若是暫時沒有任務,就暫停執行,等待下一個任務到來;若是在執行任務的過程當中有新的任務到達,也不會中斷現有任務的執行,而是添加到隊列的尾部等待promise
結論是,JS使用基於事件循環的單線程執行方式,並且是非搶斷執行的(也就是說,不管發生什麼,都會把當前任務執行完,不會出現執行到一半就去執行別的任務的狀況),你們可能會奇怪,這樣作不是性能很低嗎?確實,我們來看看這樣作的優缺點瀏覽器
多線程(C、Java等語言) | 單線程事件驅動(JavaScript) | |
---|---|---|
複雜性 | 複雜度高,須要面對線程間同步等大量消耗頭髮的問題 | 簡單易於使用,永遠不會出現資源爭搶的問題 |
性能 | CPU性能很高,適合計算密集型任務 | 單一線程,沒法發揮CPU的極限性能(可經過webWorker補充),不過前端應用本就不是計算密集型的 |
阻塞 | 不會阻塞,大型任務能夠單開線程處理 | 其實也不會阻塞,由於JS中的IO任務都是異步的(文件、網絡),雖然大型計算任務依然會阻塞UI線程,但這種狀況對前端其實很少 |
因此,JS的單線程事件循環其實很適合前端使用,大幅的簡化了程序的複雜度,同時前端少會有大型計算任務,因此性能也並不是問題markdown
結論:單線程事件循環看着好像"有點low",但其實很是適合前端開發
理解了事件循環的概念,咱們來繼續看看任務隊列,所謂任務隊列,其實就是保存待處理任務的一個數組
每當咱們要執行一個新的任務(例如:定時器),咱們就會在隊列尾部添加一個task,等到當前任務完成,事件循環會去隊列頭部尋找下一個可執行任務,咱們用一個例子來更好的理解這一點
console.log('aaaa');
setTimeout(()=>{
console.log('cccc');
}, 0); //這個0毫秒是重點
console.log('bbbb');
複製代碼
用咱們上面說的任務隊列的思想,來分析這個程序執行的過程:
console.log('aaaa')
,很普通的同步代碼console.log('bbb')
console.log('cccc')
說了這麼多,看圖更容易理解
總結一下:
你們必定注意過一個事情,那就是JS中的定時器常常不許(其實全部語言都這樣),這個問題也跟上面的任務隊列有關
因此結論就是,由於有其餘任務在排隊,定時器永遠不可能徹底準時
上面的東西你們都沒問題了的話,再給你們一個例子,檢測一下本身的學習成果
console.log('aaa');
setTimeout(() => console.log(111), 0);
setTimeout(() => console.log(222), 0);
console.log('bbb');
複製代碼
相信這個也難不倒你們了,簡單來講,setTimeout即便是0毫秒,也不會當即執行,而是堆在隊列尾部等待,而當前任務不會被打斷,因此aaa和bbb先出來,而後再從隊列尾部拿出一個任務,也就是111,而後再拿一個222
上面咱們講了關於事件循環和任務隊列的問題,那麼接下來blue要告訴你們一個驚人的事情(有啥好驚人的...)
其實js裏任務隊列不僅有一條,而是有兩條,並且有一條仍是SVIP年費白金隊列
在考慮微任務的狀況下,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');
複製代碼
Blue帶你來看一下,這個東西的執行過程
aaa
,這個沒任何疑問111
去排隊了,可是注意,定時器是宏任務222
也去排隊了,但Promise進的是VIP隊列bbb
了,並且當前任務就結束了,接下來是重點222
獲得優先執行,畢竟是VIP嘛111
的那個定時器才獲得執行因此,整個執行過程是aaa,bbb,111,222,如今咱們也明白了微任務是什麼,其實微任務就是獲得優先執行的異步任務
按照官方的設想,任務之間是不平等的,有些任務對用戶體驗影響大,就應該優先執行,而有些任務屬於背景任務(好比定時器),晚點執行沒有什麼問題,因此設計了這種優先級隊列的方式
上面咱們說到Promise也是微任務,並且async就是promise的一種語法包裝(所謂語法糖),那async是否是必定是按照微任務的方式執行呢?"不全是"
來吧,直接上個例子,在你們蒙圈以前撈一下
console.log('aaa');
(async ()=>{
console.log(111); //在async裏面
})().then(()=>{
console.log(222); //在async的then裏面
});
console.log('bbb');
複製代碼
相信我不說你們也能看出來,這個程序的坑就在111
和222
這裏,換句話說,async究竟是怎麼個異步法?
先上結果,再說緣由
aaa
,過async
是異步操做,但async函數自己(也就是111所在的()=>{}),其實依然是同步執行的,除非有await出現,這個下面會說,因此,這裏111
會直接同步執行,而不是放到隊列裏等待then
不會同步執行,它纔是異步的,並且是一個微任務,因此222
不會當即執行,而是排到隊列尾部bbb
沒什麼好說的,並且當前任務也就執行完成了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');
複製代碼
咱們來看看這個東西怎麼執行的:
aaa
不說了111
是同步執行的,上面說過222
這裏很重要了,首先,console.log本身是同步的,因此當即就會執行,咱們能直接看到222
,可是await
自己就是then
,因此console.log(333)
沒法直接執行,而是老老實實去排隊,並且,由於整個async並未執行完,它的then(也就是444)沒法觸發ddd
應該也不用說,當前任務到這裏執行完畢333
拉出來,而且執行了,這時整個async纔算完成,因此把then推到隊列中等待執行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');
複製代碼
首先,這個例子的坑很是多,不過這個例子搞定了,這課你就算畢業了,加油
aaa
,過t1
會放入任務隊列等待111
會直接執行,由於async自己不是異步的(上面有說)222
也會直接執行,可是接下來的console.log(333);
和setTimeout(()=>console.log('t2'), 0);
就塞到微任務隊列裏等待了bbb
毫無疑問,並且當前任務完成,優先執行微任務隊列,也就是console.log(333)
開始的那裏333
,而後定時器t2
會加入任務隊列等待(此時的任務隊列裏有t1和t2兩個了),而且async完成,因此console.log(444)
進入微任務隊列等待444
,此時全部微任務都完成了t1
和t2
纔會出來是時候梳理一遍Blue講過的東西了,那麼首先
感謝你們觀看這篇教程,有任何問題或想和我交流,請直接留言,發現文章有任何不妥之處,也請指出,提早感謝