1,js是單執行緒,是指主執行緒。但主執行緒之內還有很多非單執行緒的內容,比如定時器,比如promise,比如http請求。
2,在瀏覽器事件環機制中,當執行棧的同步程式碼清空後,系統會去讀取任務佇列。什麼叫執行棧的同步程式碼清空?其實就是主執行緒之內能一步過的程式碼執行完畢。那麼什麼叫任務佇列?其實就是那些非單執行緒的內容,js執行的時候,會把這些非同步的內容先儲存起來,統一放到一個佇列裡,等待主執行緒裡的同步程式碼執行完畢後再來處理這些任務,所以叫任務佇列。
3,所謂任務佇列,其實也是有分別的。這裡面分兩大類:巨集任務和微任務。常見的巨集任務包括setTimeout,setImmediate(只有ie支援),setInterval,messageChannel。常見的微任務則包括Promise.then(),mutationObserver。
4,既然任務佇列有分別,那麼處理這些任務的時候就有了優先順序————優先處理微任務。把微任務清空後(如果處理過程中有新的微任務,在處理當前微任務後會馬上處理新的微任務,並在下次執行巨集任務前全部清空),再依次讀取巨集任務,這裡特別注意,並非一次性執行完所有巨集任務,而是像佇列那樣,先取一個巨集任務執行,執行完後,再去看是否有微任務,如果有,則執行微任務,然後再讀取一個巨集任務執行,不斷迴圈。
5,node.js中的事件環跟瀏覽器裡的事件環機制又有區別:在nodejs的事件環機制中,是優先執行微任務,但是當執行完微任務,進入巨集任務的時候,即使在執行巨集任務過程中存在新的微任務,也不會優先執行微任務,而是把巨集任務佇列中執行完畢。 例子:
setTimeout(() => {
console.log('我是setTimeout1')
Promise.resolve().then(()=>{
console.log('我是Promise1')
})
}, 0);
Promise.resolve().then(()=>{
console.log('我是Promise2')
setTimeout(() => {
console.log('我是setTimeout2')
}, 0);
Promise.resolve().then(()=>{
console.log('我是Promise3')
})
console.log('我是新插進來的')
})
console.log("我是最後一個")
複製程式碼
列印順序為'我是最後一個' -> '我是Promise2' -> '我是新插進來的' -> '我是Promise3' -> '我是setTimeout1' -> '我是Promise1' -> '我是setTimeout2'
解析:
1:js執行,碰到setTimeout,丟到任務佇列裡(巨集任務),碰到promise,丟到任務佇列裡(微任務);先執行完主執行緒裡的單執行緒,也就是執行棧裡的同步程式碼,輸出“我是最後一個”。
2:開始處理任務佇列。
3:優先處理微任務,列印'我是Promise2',碰到setTimeout,繼續丟到任務佇列,向下執行,輸出'我是Promise3'。
4:微任務處理完畢,去看看任務佇列裡有沒有巨集任務。
5:處理先進去的那個setTimeout,輸出'我是setTimeout1',向下又發現了一個微任務Promise,丟到任務佇列。
6:好的,一個巨集任務已經完成了,去看看有沒有微任務,發現了剛丟進去的promise,處理一下,輸出'我是Promise1'。
7:微任務全處理了,去看看有沒有微任務,發現了最後丟進去的那個碰到setTimeout,進行處理,輸出'我是setTimeout2'。至此全部輸出完畢。