0、從個例子開始
//code-01
console.log(1)
setTimeout(() => {
console.log(2);
});
console.log(3);
稍微有點前端經驗的人都知道這段程式碼輸出的應該是 1 3 2
,因為setTimeout函式是非同步執行。
那麼都說js語言是單執行緒的,就是說是一件事從頭到尾做完,那麼它是怎麼做到非同步的呢?
這就要說到瀏覽器的執行機制了。
1、瀏覽器的基本機制
關於這部分內容目前瞭解不是很多,以後有時間再補上詳細的內容。瀏覽器程式大致分為如下幾個部分(從其他地方拷的圖):
我們所說的js單執行緒,是指瀏覽器的js引擎執行緒
只有一個,用來執行js的程式碼,而瀏覽器的定時觸發器執行緒
和事件觸發器執行緒
結合,可以實現js語言的非同步邏輯。那麼js到底是怎麼執行非同步操作的呢?
我們來看下面這張流程圖:
2、js語言事件迴圈機制-基礎
我們來根據以上流程圖,再來看 上面程式碼 code-01,
1.程式碼開始執行,遇到console.log(1) ,列印 1
2.程式碼繼續執行,遇到setTimeout,此為非同步任務,交給非同步處理模組
(這裡可能是定時觸發器執行緒
),因為沒有延遲時間,所以console.log(2)
很快加入到了事件佇列
中,因為同步任務沒有執行完,所以現在不能執行
3.程式碼繼續執行,遇到console.log(3),列印 3
4.程式碼同步任務執行完畢,檢視事件佇列
中是否有任務,發現有console.log(2)
,於是列印 2
3、js語言事件迴圈機制-巨集任務與微任務
經過上面的分析,我們對 事件迴圈機制有了初步的瞭解,現在我們再來看一個例子:
// code-02
console.log(1)
setTimeout(() => {
console.log(2);
});
new Promise(function(resolve){
console.log(3)
resolve()
}).then(function(){
console.log(4)
})
console.log(5);
上面程式碼的結果為1 3 5 4 2
,
我們知道 promise.then和setTimeout都是非同步事件,那為什麼then會比setTimeout先執行呢?
其實是因為上面流程圖中 事件佇列
其實應該分為 巨集任務佇列
和微任務佇列
,微任務
優先於巨集任務
,而且要等微任務佇列
清空,才會去取巨集任務佇列
中的任務。
所以以上流程圖應改為:
我們再來根據以上更新的流程圖,再來看 上面程式碼 code-02,
1.程式碼開始執行,遇到console.log(1) ,列印 1
2.程式碼繼續執行,遇到setTimeout,此為非同步任務,交給非同步處理模組
,因為沒有延遲時間,所以console.log(2)
很快加入到了巨集任務佇列
中
3.程式碼繼續執行,遇到console.log(3),列印 3
4.程式碼繼續執行,遇到then函式,此為非同步任務,交給非同步處理模組
,因為promise馬上就resolve,所以console.log(4)
很快加入到了微任務佇列
中
5.程式碼繼續執行,遇到遇到console.log(5) ,列印 5
6.程式碼同步任務執行完畢,檢視微任務佇列
中是否有任務,發現有console.log(4)
,於是 列印 4
7.微任務佇列
被清空,檢視巨集任務佇列
中是否有任務,發現有console.log(2)
,於是 列印 2
那麼到底有哪些非同步任務是巨集任務
,哪些是微任務
呢?
常見的巨集任務
1.script程式碼(整體的外層程式碼其實就是第一個巨集任務)
2.setTimeout,setInterval,setImmediate
3. i/o事件
4. UI事件,比如點選事件
常見的微任務
promise
process.nextTick(Node.js)
4、最後一個例子 - 最少延遲時間
我們再來看最後一個例子
setTimeout(() => {
console.log(1);
},2);
setTimeout(() => {
console.log(2);
},1);
setTimeout(() => {
console.log(3);
},0);
執行結果為 2 3 1
可能會有人疑惑,照以上的邏輯,不應該是 3 2 1
嗎? 這是因為 setTimeout官方給出的規定是:最低延遲為 4ms,(這個有限制條件,但沒怎麼看懂)
但這個最低時間不同環境好像實現的不太一樣
就上面程式碼而言,在Chorme瀏覽器中,最低延遲1ms,就是說 0ms 和 1ms 是同樣的,
所以根據程式碼順序,console.log(2)
比console.log(3)
先進入 巨集任務佇列
5、總結
- js是單執行緒,只能順序執行程式碼, 但是瀏覽器有其他執行緒可以處理非同步情況
- js引擎執行程式碼時,遇到同步任務則順序執行,遇到非同步任務則交由
非同步事件處理模組
處理非同步事件處理模組
等事件觸發條件達成後,將非同步任務分別 加入巨集任務佇列
和微任務佇列
- 同步任務執行完畢後,先執行
微任務佇列
任務,等佇列清空時,執行巨集任務佇列
- 每一個
巨集任務
重複 2 步驟
參考
1.Event Loop的規範和實現
2.這一次,徹底弄懂 JavaScript 執行機制
3.setTimeout和setImmediate到底誰先執行,本文讓你徹底理解Event Loop