JS 時間循環 - 宏任務與微任務

1、關於 JavaScript

JavaScript 是一門單線程語言,在最新的 html5 中提出了 Web-Worker,但 javascript 是單線程這一核心仍未改變。所以一切 javascript 版的 "多線程" 都是用單線程模擬出來的,一切 javascript 多線程都是紙老虎!

2、javascript 事件循環

既然 js 是單線程,那就像只有一個窗口的銀行,客戶需要排隊一個一個辦理業務,同理 js 任務也要一個一個順序執行。如果一個任務耗時過長,那麼後一個任務也必須等着。

那麼問題來了,假如我們想瀏覽新聞,但是新聞包含的超清圖片加載很慢,難道我們的網頁要一直卡着直到圖片完全顯示出來?因此聰明的程序員將任務分爲兩類:

當我們打開網站時,網頁的渲染過程就是一大堆同步任務,比如頁面骨架和頁面元素的渲染。而像加載圖片音樂之類佔用資源大耗時久的任務,就是異步任務。

關於這部分有嚴格的文字定義,但本文的目的是用最小的學習成本徹底弄懂執行機制,所以我們用導圖來說明:

導圖要表達的內容用文字來表述的話:

我們不禁要問了,那怎麼知道主線程執行棧爲空啊?js 引擎存在 monitoring process 進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去 Event Queue 那裏檢查是否有等待被調用的函數。

3、Promise 與 process.nextTick(callback)

Promise 的定義和功能本文不再贅述,不瞭解的讀者可以學習一下阮一峯老師的 Promise。而 process.nextTick(callback) 類似 node.js 版的 "setTimeout",在事件循環的下一次循環中調用 callback 回調函數。

我們進入正題,除了廣義的同步任務和異步任務,我們對任務有更精細的定義:

不同類型的任務會進入對應的 Event Queue,比如 setTimeout 和 setInterval 會進入相同的 Event Queue。

事件循環的順序,決定 js 代碼的執行順序。進入整體代碼 (宏任務) 後,開始第一次循環。接着執行所有的微任務。然後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。

事件循環的進程模型

執行棧在執行完同步任務後,查看執行棧是否爲空,如果執行棧爲空,就會去檢查微任務 (microTask) 隊列是否爲空,如果爲空的話,就執行 Task(宏任務),否則就一次性執行完所有微任務。

每次單個宏任務執行完畢後,檢查微任務 (microTask) 隊列是否爲空,如果不爲空的話,會按照先入先出的規則全部執行完微任務 (microTask) 後,設置微任務 (microTask) 隊列爲 null,然後再執行宏任務,如此循環。

例子:

 輸出結果:

1 7 6 8 2 4 3 5 9 11 10 12

4.process.nextTick 和 Promise 都是 Microtasks(微任務),爲什麼 process.nextTick 會先執行?

rocess.nextTick 永遠大於 promise.then,原因其實很簡單。。。在 Node 中,_tickCallback 在每一次執行完 TaskQueue 中的一個任務後被調用,而這個_tickCallback 中實質上幹了兩件事:

1、nextTickQueue 中所有任務執行掉 (長度最大 1e4,Node 版本 v6.9.1)

2、第一步執行完後執行_runMicrotasks 函數,執行 microtask(微任務)中的部分 (promise.then 註冊的回調)

所以很明顯 process.nextTick > promise.then

5、總結

(1)js 的異步

我們從最開頭就說 javascript 是一門單線程語言,不管是什麼新框架新語法糖實現的所謂異步,其實都是用同步的方法去模擬的,牢牢把握住單線程這點非常重要。

(2) 事件循環 Event Loop

事件循環是 js 實現異步的一種方法,也是 js 的執行機制。

(3)javascript 的執行和運行

執行和運行有很大的區別,javascript 在不同的環境下,比如 node,瀏覽器,Ringo 等等,執行方式是不同的。而運行大多指 javascript 解析引擎,是統一的。

(4)setImmediate

微任務和宏任務還有很多種類,比如 setImmediate 等等,執行都是有共同點的,有興趣的同學可以自行了解。

(5) 最後的最後

**6、深入淺出分析 process.nextTick() **

process.nextTick() 是 Node 的一個定時器,讓任務可以在指定的時間運行。其中 Node 一共提供了 4 個定時器,它們分別是 setTimeout()、setInterval()、setImmediate()、process.nextTick()。

process.nextTick() 這個名字有點誤導,它是在本輪循環執行的,而且是所有異步任務裏面最快執行的。

Node 執行完所有同步任務,接下來就會執行 process.nextTick 的任務隊列。所以,下面這行代碼是第二個輸出結果。

process.nextTick(() => console.log(3));

基本上,如果你希望異步任務儘可能快地執行,那就使用 process.nextTick。

根據語言規格,Promise 對象的回調函數,會進入異步任務裏面的” 微任務”(microtask)隊列。
微任務隊列追加在 process.nextTick 隊列的後面,也屬於本輪循環。所以,下面的代碼總是先輸出 3,再輸出 4。

process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4)); // 3 // 4

注意,只有前一個隊列全部清空以後,纔會執行下一個隊列。

process.nextTick(() => console.log(1));
Promise.resolve().then(() => console.log(2));
process.nextTick(() => console.log(3));
Promise.resolve().then(() => console.log(4)); // 1 // 3 // 2 // 4

上面代碼中,全部 process.nextTick 的回調函數,執行都會早於 Promise 的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/sryIBteJ_9Dqc0bZdFTX9w