一、JavaScript是單線程單並發語言
-
什么是單線程
主程序只有一個線程,即同一時間片斷內其只能執行單個任務。
-
為什么選擇單線程?
JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。
-
單線程意味着什么?
單線程就意味着,所有任務都需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就需要一直等着。這就會導致IO操作(耗時但cpu閑置)時造成性能浪費的問題。
-
如何解決單線程帶來的性能問題?
答案是異步!主線程完全可以不管IO操作,暫時掛起處於等待中的任務,先運行排在后面的任務。等到IO操作返回了結果,再回過頭,把掛起的任務繼續執行下去。於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)
注: 當主線程阻塞時,任務隊列仍然是能夠被推入任務的
二、事件循環Event Loop
1、JavaScript 內存模型圖
2、JavaScript 代碼執行機制
-
所有同步任務都在主線程上的棧中執行。
-
主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
-
一旦"棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",選擇出需要首先執行的任務(由瀏覽器決定,並不按序)。
3、事件循環(EventLoop)
三、異步任務
1.MacroTask(宏觀Task) setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
2.MicroTask(微觀任務) process.nextTick, Promise, Object.observe, MutationObserver
規范:
- 每個瀏覽器環境,至多有一個event loop。
- 一個event loop可以有1個或多個MacroTask queue,而僅有一個 MicroTask Queue。
- 一個task queue是一列有序的task, 每個task定義時都有一個task source,從同一個task source來的task必須放到同一個task queue,從不同源來的則被添加到不同隊列。
- tasks are scheduled,所以瀏覽器可以從內部到JS/DOM,保證動作按序發生。
-
Microtasks are scheduled,Microtask queue 在當前 task queue 的結尾執行。microtask中添加的microtask也被添加到Microtask queue的末尾並處理。
注: event loop的每個turn,是由瀏覽器決定先執行哪個task queue。這允許瀏覽器為不同的task source設置不同的優先級,比如為用戶交互設置更高優先級來使用戶感覺流暢。
四、實例
```javascript
function ELoop() { // 當前任務 let p = new Promise((resolve, reject) => { console.log("current Task") resolve(); }); let nextP;
setTimeout(() => {
console.log("MacroTask_1");
nextP.then(() => {
// 第一次執行時,這段代碼並沒有執行到。
console.log("MicroTask_promise_1"); //第一個MicroTask
})
console.log("MacroTask_1 end")
}, 0) // 第一個 MacroTask
setTimeout(() => {
console.log("MacroTask_2");
console.log("MacroTask_2 end")
}, 0) // 第二個MacroTask
nextP = p.then(() => {
console.log("MicroTask_promise_2"); //第一個MicroTask
}).then(() => {
console.log("MicroTask_promise_3"); // 第二個MicroTask
})
console.log("current Task end")
}
ELoop();
/**輸出結果:
current Task
current Task end
MicroTask_promise_2
MicroTask_promise_3
MacroTask_1
MacroTask_1 end
MicroTask_promise_1
MacroTask_2
MacroTask_2 end
**/
```