1.關於JavaScript
JavaScript是一門單線程語言,在最新的html5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。所以一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!
2.javascript事件循環
既然js是單線程,那就像只有一個窗口的銀行,客戶需要排隊一個一個辦理業務,同理js任務也要一個一個順序執行。如果一個任務耗時過長,那么后一個任務也必須等着。那么問題來了,假如我們想瀏覽新聞,但是新聞包含的超清圖片加載很慢,難道我們的網頁要一直卡着直到圖片完全顯示出來?因此聰明的程序員將任務分為兩類:
- 同步任務
- 異步任務
當我們打開網站時,網頁的渲染過程就是一大堆同步任務,比如頁面骨架和頁面元素的渲染。而像加載圖片音樂之類占用資源大耗時久的任務,就是異步任務。關於這部分有嚴格的文字定義,但本文的目的是用最小的學習成本徹底弄懂執行機制,所以我們用導圖來說明:
導圖要表達的內容用文字來表述的話:
- 同步和異步任務分別進入不同的執行"場所",同步的進入主線程,異步的進入Event Table並注冊函數。
- 當指定的事情完成時,Event Table會將這個函數移入Event Queue。
- 主線程內的任務執行完畢為空,會去Event Queue讀取對應的函數,進入主線程執行。
- 上述過程會不斷重復,也就是常說的Event Loop(事件循環)。
我們不禁要問了,那怎么知道主線程執行棧為空啊?js引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調用的函數。
3.Promise與process.nextTick(callback)
Promise的定義和功能本文不再贅述,不了解的讀者可以學習一下阮一峰老師的Promise。而process.nextTick(callback)類似node.js版的"setTimeout",在事件循環的下一次循環中調用 callback 回調函數。
我們進入正題,除了廣義的同步任務和異步任務,我們對任務有更精細的定義:
- macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
- micro-task(微任務):Promise,process.nextTick
不同類型的任務會進入對應的Event Queue,比如setTimeout和setInterval會進入相同的Event Queue。
事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)后,開始第一次循環。接着執行所有的微任務。然后再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。
事件循環的進程模型
- 選擇當前要執行的任務隊列,選擇任務隊列中最先進入的任務,如果任務隊列為空即null,則執行跳轉到微任務(MicroTask)的執行步驟。
- 將事件循環中的任務設置為已選擇任務。
- 執行任務。
- 將事件循環中當前運行任務設置為null。
- 將已經運行完成的任務從任務隊列中刪除。
- microtasks步驟:進入microtask檢查點。
- 更新界面渲染。
- 返回第一步。
執行棧在執行完同步任務后,查看執行棧是否為空,如果執行棧為空,就會去檢查微任務(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
https://www.51220.cn 51220網站目錄
5.總結
(1)js的異步
我們從最開頭就說javascript是一門單線程語言,不管是什么新框架新語法糖實現的所謂異步,其實都是用同步的方法去模擬的,牢牢把握住單線程這點非常重要。
(2)事件循環Event Loop
事件循環是js實現異步的一種方法,也是js的執行機制。
(3)javascript的執行和運行
執行和運行有很大的區別,javascript在不同的環境下,比如node,瀏覽器,Ringo等等,執行方式是不同的。而運行大多指javascript解析引擎,是統一的。
(4)setImmediate
微任務和宏任務還有很多種類,比如setImmediate等等,執行都是有共同點的,有興趣的同學可以自行了解。
(5)最后的最后
- javascript是一門單線程語言
- Event Loop是javascript的執行機制
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 的。