前言
JavaScript是一門單執行緒的弱型別語言,但是我們在開發中,經常會遇到一些需要非同步或者等待的處理操作。
類似ajax,亦或者ES6中新增的promise操作用於處理一些回撥函式等。
概念
在JavaScript程式碼執行過程中,可以分為同步佇列和非同步佇列。
-
同步任務類似我們常說的立即執行函式,不需要等待可以直接進行,可以直接進入到主執行緒中去執行,類似正常的函式呼叫等。
-
非同步佇列則是非同步執行函式,類似ajax請求,我們在發起的過程中,會進入到一個非同步佇列,載入到任務當中時,需要進行等待,之後才能進行返回值的處理。
舉個例子
下面一段程式碼,我們可以先了解一些一些關於事件迴圈機制的一些基本的原理
console.log('1');
setTimeout(function() {
console.log('4');
}, 0);
Promise.resolve().then(function() {
console.log('2');
}).then(function() {
console.log('3');
});
console.log('5');
我們將程式碼列印到控制檯當中,輸出結果是:1,5,2,3,4
我們知道,在JavaScript中,類似定時器,以及ES6新增的promise是非同步函式,回到我們上面所說的佇列的概念當中,不難得出,1和5為同步執行佇列
在執行完同步佇列中的程式碼之後,再執行非同步佇列中的程式碼。
TIP
在解析非同步佇列的promise和定時器中,我們發現,定時器setTimeout是後執行於promise,這裡我們引入JavaScript規範中的巨集任務(Macro Task)和微任務(Micro Task)的概念
在JavaScript中,巨集任務包含了:script( 整體程式碼)、setTimeout、setInterval、I/O、UI 互動事件、setImmediate(Node.js 環境)
微任務:Promise、MutaionObserver、process.nextTick(Node.js 環境)
再回到上面的定時器和promise的問題,這時候我們知道,JavaScript中,當有非同步佇列的時候,優先執行微任務,再執行巨集任務
再次舉個例子
假如在非同步佇列當中存在非同步佇列時,我們需要怎麼處理
console.log(1);
setTimeout(function() {
console.log(5);
}, 10);
new Promise(resolve => {
console.log(2);
resolve();
setTimeout(() => console.log(3), 10);
}).then(function() {
console.log(4);
})
console.log(6);
將程式碼執行到控制檯中,得出的列印順序是:1,2,6,4,5,3
-
不同於例子1當中的promise,列印2是優先於6執行的,由此我們可以知道,new Promise在執行過程中,在未執行resolve或者rejected前,所執行的程式碼均為同步佇列中的程式碼。
-
再看4,5,3的執行順序,在執行微任務promise執行回撥resolve之後,對應的then立即執行
-
在列印結果中,定時器5優先執行於---->屬於微任務promise中的巨集任務定時器3,定時器5這個巨集任務是在promise微任務這個佇列之後就加進去,在promise執行完成then回撥之後,promise中的巨集任務才加入到佇列當中,因此在定時器5之後執行
總結
在JavaScript中,巨集任務包含了:script( 整體程式碼)、setTimeout、setInterval、I/O、UI 互動事件、setImmediate(Node.js 環境)
微任務:Promise、MutaionObserver、process.nextTick(Node.js 環境);
在執行過程中,同步程式碼優先於其他任務佇列中的程式碼,
定時器,promise這類任務,在執行過程中,會先加入佇列,
在執行完同步程式碼之後,再根據巨集任務和微任務的分類,先執行微任務佇列,再執行巨集任務佇列。
文章個人部落格地址:JavaScript的事件迴圈機制