JS併發模型與Event Loop

1. JS單線程執行

JS的執行是單線程的,也就是同一段時間只能作一件事。單線程執行意味着全部的任務須要排隊,只有前一個任務執行完畢,才能執行後一個任務。爲了充分提升JS執行的效率,全部的任務分爲同步任務和異步任務。html

1)同步任務ajax

同步任務指的是在主線程上排隊執行的任務,只有前一個任務執行完,才能執行後一個任務。瀏覽器

2)異步任務bash

異步任務指的不進入主線程,而是進入「任務隊列」(task queue)裏的任務。只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。併發

那麼JS是如何處理這些同步任務和異步任務呢?看下面介紹JS執行的併發模型。dom

2. 事件循環

JavaScript的併發模型基於「事件循環」機制。異步

1)Stack 執行棧函數

函數調用造成了一個棧幀。當函數被調用時,會建立一個執行幀,幀中包含該函數的參數和局部變量,若是該函數內部還有函數調用,就會接着建立另外一個幀,包含內部函數的參數和局部變量。若是存在全局函數調用,則全局函數壓在幀的最底部。當函數執行完畢後,函數所在的執行幀會被彈出棧外,函數所在的變量和做用域會被釋放。執行棧事件先進後出。oop

2)Heap 堆ui

Heap指存放對象的內存區域,對象會被分配在一個堆中,即用以表示一大塊非結構化的內存區域。在函數內使用對象,實際上是指向內存中對象的一個指針。

3)Queue 隊列

一個 JavaScript 運行時包含了一個待處理的任務隊列,任務隊列是先進先出。每個任務都關聯着一個用以處理這個任務的函數。在事件循環期間的某個時刻,JS運行時會從任務隊列中取出最早進入隊列中的一個任務進行處理。這個任務會被移出隊列,任務關聯的函數會在執行棧中建立新的棧幀,被執行。執行棧清空後,事件循環會再處理隊列中的下一條任務。

當一個任務須要太長時間才能處理完畢時,能夠縮短任務處理,或者將一個任務裁剪成多個任務。在瀏覽器裏,當一個事件發生且有一個事件監聽器綁定在該事件上時,任務會被隨時添加進隊列。

setTimeout(fn, delay),delay參數是指任務被實際加入到隊列中的最小延遲時間,若是隊列中沒有其餘任務,在這個延遲時間以後,任務會立刻被處理。可是若是有其餘任務,setTimeout會等待其餘任務處理完後再執行fn。

總之,隊列中的這些任務會在主線程的執行棧被清空時被依次讀取(任務隊列先進先出,即先被壓入隊列中的事件會被先執行)。

下面的圖更詳細的介紹JS的事件循環模型。

主要操做邏輯以下:

1)執行棧執行主線程任務,當有操做dom,ajax交互,定時器等操做時,這些任務會被移入到callback queue 任務隊列中。

2)當主線程任務執行完畢爲空時,會讀取callback queue隊列中的函數,進入主線程執行。

3)上述過程會不斷重複,造成Event Loop。

4. Macrotasks 和 Microtasks

在一個事件循環中,異步事件返回結果後會被放入到任務隊列中。根據異步事件的類型,會被放入到 對應的宏任務列表或者微任務列表中,當執行棧爲空的時候,主線程會首先查看微任務隊列中的事件,執行微任務隊列中的事件後,再執行宏任務隊列中的事件。

1)Macrotasks 宏任務

包括 setTimeout, setInterval, setImmediate, I/O, UI rendering

2)Microtasks 微任務

包括 process.nextTick, Promises, Object.observe(廢棄), MutationObserver

console.log('1');
setTimeout(function() {
  console.log('2');
}, 0);
Promise.resolve().then(function() {
  console.log('3');
}).then(function() {
  console.log('4');
});
console.log('5');

//輸出值
1
5
3
4
undefined
2
複製代碼

microtask會優先macrotask執行。在每一次事件循環中,macrotask 只會提取一個執行,而 microtask 會一直提取,直到 microtasks 隊列清空。

參考資料

JavaScript併發模型與Event Loop

併發模型與事件循環

JavaScript 運行機制詳解:再談Event Loop

相關文章
相關標籤/搜索