先來看一張圖片
主執行緒呼叫棧
所謂的JS單執行緒是指一個瀏覽器的主程式中只有一個JS執行執行緒。JS的執行執行緒的呼叫棧(stack)都是同步的(因為是單執行緒,所以一次只能做一件事),JS執行執行緒的主執行緒上的所有同步的任務都會放在這個執行棧中。
console.log('event loop1');
console.log('event loop2');
複製程式碼
簡單如以上程式碼,就被放在Call Stack執行棧中順序執行,然後遵循先進後出,後進先出的原則銷燬。輸出的順序是:先是event loop1後是 event loop2。
事件迴圈機制
如果執行過程中遇到DOM操作、ajax或setTimeout等非同步操作的時候,會提交給相應的非同步程式處理。
如:DOM操作是瀏覽器核心的DOM binding模組處理;Ajax是瀏覽器核心的Network模組處理;setTimeout是由瀏覽器核心的Timer模組處理。
處理完的回撥就被推入到任務佇列callback queue(應該叫event queue可能更準確些)。等待主執行緒的呼叫棧空閒。
一旦主執行緒的task執行完畢後,瀏覽器執行環境的Event Loop機制就查詢任務佇列,把佇列裡排在前面的回撥壓入主呼叫棧執行,如此重複。這個過程就稱為事件迴圈。
所以我們就可以很容易理解以下程式碼:
console.log('hello');
setTimeout(function(){
console.log('setTimeout1');
})
setTimeout(function(){
console.log('setTimeout2');
})
console.log('world');
複製程式碼
結果依次是:hello world setTimeout1 setTimeout2
任務佇列
任務佇列存在兩種型別,一種是microtask queue,還有一種是macrotask queue。
macro-task(巨集任務)包括:script(整體程式碼),setTimeout,setInterval, setImmediate(node.js),I/O,UI rendering。
micro-task(微任務)包括:process.nextTick(node.js),Promises,Object.observe, MutationObserver。
事件迴圈主執行緒進入呼叫棧開始,看到setTimeout處理完就會新增macrotask 佇列中。看到Promises處理完就會新增到microtask佇列中。等到主執行緒呼叫棧都執行完任務後,會優先按執行microtask queue,全部執行完。然後才會取macrotask queue排在隊首的執行。
看下面這段程式碼:
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
new Promise((resolve, reject) => {
console.log(3);
Promise.resolve().then(() => {
console.log(4);
})
resolve();
}).then(() => {
console.log(6);
})
console.log(7);
複製程式碼
執行順序依次是:1 3 7 4 6 2 。這說明mic-rotask中的回撥將優先執行。
ps:這個網站可以實現程式碼的視覺化 latentflip.com/loupe/,有興趣的可以看看。
參考文章: