javascript事件迴圈分為2種:一種是瀏覽器端事件迴圈,一種是node端事件迴圈。 此文只是捋一捋我對瀏覽器端事件迴圈的理解。
前言
我們都知道 JavaScript 是一門單執行緒語言,這意味著同一時間只能執行一個任務,結束了才能去執行下一個。如果前面的任務沒有執行完,後面的任務就會一直等待。試想,有一個耗時很長的網路請求,如果所有任務都需要等待這個請求完成才能繼續,顯然是不合理的並且我們在瀏覽器中也沒有體驗過這種情況(除非你要同步請求 Ajax),究其原因,是 JavaScript 藉助非同步機制來實現了任務的排程。
JavaScript 常見的非同步有3種:ajax、dom事件(button的點選事件等)、定時器(setTimeout等)。
舉個例子
console.log("A");
setTimeout(() => {
console.log("B");
}, 100);
console.log("C");
複製程式碼
稍微瞭解一點瀏覽器中非同步機制的同學都能答出會輸出 “A C B”,我們來通過分析 event loop 來對瀏覽器中的非同步進行梳理,並搞清上面的問題。
從上圖我們可以看出js分為3個部分:stack(主執行緒)、WebAPIs、event loop。
瀏覽器的執行順序是:
1.先執行主執行緒,即
console.log("A");
setTimeout(() => {
console.log("B");
}, 100);
console.log("C");
複製程式碼
程式從上往下執行,但是執行到setTimeout時,只是註冊了一個WebAPIs事件,並不會執行setTimeout的回撥函式。 此回撥函式是非同步的,會在主執行緒的所有程式執行完畢後根據註冊的時間才執行。
2.當主程式執行完畢後,就執行event loop。當然callback queue一開始是空的。event loop是一個‘死迴圈’,它會一遍又一遍的去檢測WebAPIs有沒有可執行的回撥函式,有就把回撥函式放入自身的callback queue並執行,沒有的話繼續下一次迴圈。
3.瀏覽器監聽WebAPIs事件。過了100ms定時器事件觸發了,它會通知event loop有可執行的回撥函式了。
事件迴圈的一個必須條件是 執行棧中的任務執行完畢才會執行 “任務佇列”中的任務,如果執行棧一直處於執行不完的情況,也就沒有任務佇列什麼事了。所以我們的settimeout 後面的引數其實是不準確的,他開始算的時間應該是執行棧的任務執行完畢。
settimeout還有一個最小時間4ms的規定,所以當我們寫settimeout(function(){},0)時,其中的0其實是4ms。
微任務和巨集任務
首先說明,是以__瀏覽器為處理環境__下的執行邏輯 瀏覽器環境下的微任務和巨集任務有哪些 巨集任務:setTimeout setImmediate MessageChannel 微任務:Promise.then MutationObserver 記住兩點:
- 微任務在巨集任務之前的執行,先執行 執行棧中的內容 執行後 清空微任務
- 每次取一個巨集任務 就去清空微任務,之後再去取巨集任務
題目
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(()=>{
console.log('then1');
});
},0)
Promise.resolve().then(()=>{
console.log('then2');
Promise.resolve().then(()=>{
console.log('then3');
})
setTimeout(function(){
console.log('setTimeout2');
},0)
})
複製程式碼
- 微任務巨集任務同時出現,先將微任務放入執行佇列,棧為空則先執行then2。setTimeout1放入巨集任務執行佇列中
- then2之後執行後,接下來存在微任務then3。將then3放入微任務佇列中。
- 接下來setTimeout2加入到巨集任務佇列中。
- 此時執行棧為空,執行then3。
- 微任務全部執行完畢後,執行巨集任務setTimeout1,執行發現微任務then1,放置到微任務佇列中。
- setTimeout1巨集任務執行完,再次清空微任務佇列,執行then1
- 微任務全部執行完畢後,執行巨集任務setTimeout2。程式結束。