事件循環Event Loop
我們都知道,JavaScript 是一種單線程的編程語言,簡單的說就是:js只有一條通道,那么在任務多的情況下,就會出現擁擠的情況,這種情況下就產生了 ‘多線程’ ,但是這種“多線程”是通過單線程模仿的,也就是假的。
這意味著它一次只能執行一個任務。但是,通過事件循環(Event Loop)機制,可以處理異步操作并確保程序的高效運行。理解事件循環對于我們前端開發者來說至關重要!
事件循環的基本概念
事件循環的核心在于任務隊列(Task Queue),它負責管理和調度各種任務的執行。任務分為兩類:宏任務、微任務。
事件循環的執行步驟
事件循環的基本執行步驟如下:
- 執行一個宏任務(例如,從頭到尾運行一個 script)。
- 執行所有的微任務(一直執行,直到微任務隊列為空)。
- 更新渲染(如果需要)。
- 繼續執行下一個宏任務。
看不懂沒關系,結合下面的例子就能明白了!
宏任務和微任務詳解
基于上面的基礎知識,我們詳細介紹下宏任務和微任務
為什么要區分宏任務和微任務?
(1)js是單線程的,但是分同步異步
(2)微任務和宏任務皆為異步任務,它們都屬于一個隊列
(3)宏任務一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 環境)
(4)微任務:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 環境)
(5)先執行同步再執行異步,異步遇到微任務,先執行微任務,執行完后如果沒有微任務,就執行下一個宏任務,如果有微任務,就按順序一個一個執行微任務
宏任務、微任務有哪些?
- 宏任務一般是:script、setTimeout、setInterval、postMessage、MessageChannel、setImmediate(Node.js 環境)
- 微任務:Promise.then、Object.observe、MutationObserver、process.nextTick(Node.js 環境)
宏任務、微任務是怎么執行的?
簡單來說,宏任務、微任務的執行順序是:先執行同步代碼,異步任務放入任務隊列中,當所有同步代碼執行完畢后,先執行微任務在執行宏任務。
接下來,我們將通過很多例子,幫助大家深入了解
基本事件循環
setTimeout(function(){console.log(1);
});
new Promise(function(resolve){ console.log(2);resolve();
}).then(function(){ console.log(3);
}).then(function(){console.log(4)
});
console.log(5);
// 2 5 3 4 1
分析
- setTimout是異步宏任務,放入宏任務隊列中
- new Promise在實例化的過程中所執行的代碼都是同步進行的,所以輸出2
- Promise.then是異步微任務,將其放入微任務隊列中
- 遇到同步任務console.log(5);輸出5;主線程中同步任務執行完
- 從微任務隊列中取出任務到主線程中,輸出3、 4,微任務隊列為空
- 從宏任務隊列中取出任務到主線程中,輸出1,宏任務隊列為空
console.log(1)
setTimeout(function() {console.log(2)
}, 0)
const p = new Promise((resolve, reject) => {resolve(4)
})
p.then(data => {console.log(data)
})
console.log(3)
//1,3,4,2
- 遇到同步任務console.log(1);輸出1;
- 遇到setTimeout 異步宏任務,放入宏任務隊列中;
- 遇到 Promise,new Promise在實例化的過程中所執行的代碼都是同步進行的,但由于new Promise沒有輸出事件,所以接著執行遇到.then;
- 執行.then,異步微任務,被分發到微任務Event Queue中;
- 遇到同步任務console.log(3);輸出3;
- 主線程中同步任務執行完,從微任務隊列中取出任務到主線程中,p.then 輸出4,微任務執行完畢,任務隊列為空;
- 開始執行宏任務setTimeout 輸出2,宏任務隊列為空;
嵌套的宏任務與微任務
console.log('腳本開始')
setTimeout(() => {console.log('這是setTimeout1-------------');Promise.resolve().then(() => {console.log('這是promise1');});
}, 0);setTimeout(() => {console.log('這是setTimeout2');
}, 0);Promise.resolve().then(() => {console.log('這是promise2');
});console.log('腳本結束');
執行順序:
console.log('腳本開始')
—— 同步代碼,立即執行。- 第一個
setTimeout
回調注冊到宏任務隊列。 - 第二個
setTimeout
回調注冊到宏任務隊列。 Promise
回調注冊到微任務隊列。console.log('腳本結束')
—— 同步代碼,立即執行。- 當前宏任務執行完畢,執行微任務隊列:
console.log('這是promise2')
- 微任務執行完畢,執行第一個宏任務隊列:
console.log('這是setTimeout1-------------')
- 注冊新的微任務
console.log('這是promise1')
到微任務隊列。
- 執行微任務隊列:
console.log('這是promise1')
- 執行第二個宏任務隊列:
console.log('這是setTimeout2')
輸出結果:
腳本開始
腳本結束
這是promise2
這是setTimeout1-------------
這是promise1
這是setTimeout2
多個微任務
console.log('腳本開始');setTimeout(() => {console.log('這是setTimeout');
}, 0);Promise.resolve().then(() => {console.log('這是promise1');
}).then(() => {console.log('這是promise2');
}).then(() => {console.log('這是promise3');
});console.log('腳本結束');
執行順序:
console.log('腳本開始')
—— 同步代碼,立即執行。setTimeout
回調注冊到宏任務隊列。Promise
回調注冊到微任務隊列。console.log('腳本結束')
—— 同步代碼,立即執行。- 當前宏任務執行完畢,執行微任務隊列:
console.log('這是promise1')
- 注冊新的微任務
console.log('這是promise2')
到微任務隊列。 - 執行微任務隊列:
console.log('這是promise2')
- 注冊新的微任務
console.log('這是promise3')
到微任務隊列。 - 執行微任務隊列:
console.log('這是promise3')
- 微任務執行完畢,執行宏任務隊列:
console.log('這是setTimeout')
輸出結果:
腳本開始
腳本結束
這是promise1
這是promise2
這是promise3
這是setTimeout
總結
通過這些詳細的例子,相信大家對宏任務與微任務一定有了更深入的了解。希望本文對大家有幫助~感謝支持