大致流程
我們可以看出:
- js程式碼分為同步任務和非同步任務。
- 同步任務會進入主執行緒,非同步任務會進入Event Table(事件表),當事件表中的非同步任務完成後會在Event Queue(事件佇列)中註冊回撥函式。
- 主執行緒任務全部完成後,才會完成Event Queue中的任務。
- js解析器會不斷地重複檢查主執行緒執行棧是否為空,然後重複第3步,這就是Event Loop(事件迴圈)。
js程式碼的型別分為:
注意
- 任務又可以進一步分為巨集任務和微任務,這對js程式碼的執行有更為細緻的影響,在文章下面會有講解。
- 非同步任務中的巨集任務和微任務會進入不同的Event Queue事件佇列,即Event Queue又可以分為巨集任務佇列和微任務佇列。
- setInterval會按照設定的時間間隔重複地在Event Queue註冊回撥函式,如果某一段時間主執行緒程式碼執行太久,那麼setInterval的回撥函式可能阻塞到一起執行,無法保持設定的時間間隔,如果此時setInterval用於動畫,則體現為卡頓。
詳細流程
在事件迴圈(主執行緒 → 事件佇列)中其實有更細緻的操作流程,即(巨集任務 → 微任務)之間的迴圈,如下圖所示:
所以通常來說,我們頁面中的js執行順序是這樣的:- 第一輪事件迴圈:
- 主執行緒執行js整段程式碼(巨集任務),將ajax、setTimeout、promise等回撥函式註冊到Event Queue,並區分巨集任務和微任務。
- 主執行緒提取並執行Event Queue 中的ajax、promise等所有微任務,並註冊微任務中的非同步任務到Event Queue。
- 第二輪事件迴圈:
- 主執行緒提取Event Queue 中的第一個巨集任務(通常是setTimeout)。
- 主執行緒執行setTimeout巨集任務,並註冊setTimeout程式碼中的非同步任務到Event Queue(如果有)。
- 執行Event Queue中的所有微任務,並註冊微任務中的非同步任務到Event Queue(如果有)。
- 類似的迴圈:巨集任務每執行完一個,就清空一次事件佇列中的微任務。
注意:事件佇列中分“巨集任務佇列”和“微任務佇列”,每執行一次任務都可能註冊新的巨集任務或微任務到相應的任務佇列中,只要遵循“每執行一個巨集任務,就會清空一次事件佇列中的所有微任務”這一迴圈規則,就不會弄亂。
例項
例項1
console.log('1');
// 1 6 7 2 4 5 9 10 11 8 3
// 記作 set1
setTimeout(function () {
console.log('2');
// set4
setTimeout(function() {
console.log('3');
});
// pro2
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
// 記作 pro1
new Promise(function (resolve) {
console.log('6');
resolve();
}).then(function () {
console.log('7');
// set3
setTimeout(function() {
console.log('8');
});
})
// 記作 set2
setTimeout(function () {
console.log('9');
// 記作 pro3
new Promise(function (resolve) {
console.log('10');
resolve();
}).then(function () {
console.log('11');
})
})
複製程式碼
- 第一輪事件迴圈:
- 整體script作為第一個巨集任務進入主執行緒,遇到console.log,輸出1。
- 第二輪事件迴圈:
- 主執行緒執行佇列中第一個巨集任務set1,輸出2;程式碼中遇到了set4,註冊回撥;又遇到了pro2,new promise()直接執行輸出4,並註冊回撥;
- 第三輪事件迴圈
- 主執行緒執行佇列中第一個巨集任務set2,輸出9;程式碼中遇到了pro3,new promise()直接輸出10,並註冊回撥;
- set2巨集任務執行完畢,開始情況微任務,主執行緒執行微任務pro3,輸出11。
- 類似迴圈...
所以最後輸出結果為1、6、7、2、4、5、9、10、11、8、3
。