PS:有人對promise部分迷惑,Promise自己構造函數是同步的,.then是異步。---- 2018/7/6 22:35修改javascript
javascript 是一門單線程的腳本語言,雖然是單線程可是有不少異步的API來幫助開發者解決線程的阻塞問題。好比:onClick 註冊的回調函數、必不可少的ajax等等...可是 javascript 運行環境是如何作到單線程卻又不是一直阻塞線程等待各類異步操做完成才繼續執行操做的呢?
答案就是: event loophtml
1.event loop 的規範是在HTML5中規定的。 2.event loop 是 javascript 運行環境(手動加粗) 的機制。 3.瀏覽器實現的event loop 與 NodeJS 實現的event loop 是有異同的。
HTML5 中定義 event loop 規範連接 https://www.w3.org/TR/html5/w...
一 瀏覽器的event loophtml5
1.簡單瞭解java
event loop 即事件循環,它究竟是什麼結構呢? 阮一峯老師的博客有一張圖,雖然很直白、明瞭可是少了一些東西不能全面的將 event loop 總體循環機制展現出來。先來看圖:
node
圖片非筆者原創,來自阮一峯博客,在此說明,侵刪。web
從圖中咱們能夠獲得信息是:ajax
1.javascript 引擎執行 javascript 是單線程的,由於只有一個 stack 裏面有各類正在執行、等待執行的事件。
2.有一些 webAPI 將執行時產生的 callback 放入一個隊列,即 「事件隊列」。
3.在event loop 循環中不停的將「事件隊列」裏等待執行的事件,推入 javascript 執行棧。
這就是事件循環簡化的機制,爲何說簡化呢?由於在循環中還作了不少沒有說起的操做、規則。api
我就不舉栗子了,可是我要打個比方。
promise
就說一個老生常談的問題 (文章編輯不便,直接一行了,換行黨你卻是來打我啊!)瀏覽器
setTimeout(e=>{ console.log(1) },0); new Promise((res,rej)=>{ res() }).then(e=>{ console.log(2) });
一樣都是 javascript 中提供的異步API,一樣都是直接執行( 開發者所但願的,雖然會由於阻塞致使延時,防止槓精 ),可是不論這倆行代碼誰上、誰下,輸出都會是 2 1。由於這裏涉及 event loop 中 macro task 與 micro task 的執行順序、規則。
2.總體流程
回到剛纔說那張流程圖不夠完善的問題上,如今來一張完整的、全面的 event loop 流程圖。
圖片非筆者原創,來secrets of javascript ninja,在此說明,侵刪。
這是一個 event loop 完整的流程圖,從圖中咱們看到了許多剛纔未說起的名詞,從頭至尾的梳理一遍 (從上至下):
1.讀取 Macrotask queue 中任務。有倆種狀況
2.讀取 Microtask queue 中任務。有倆種狀況
3.根據 本次循環耗時(手動+文章加粗)判斷是否 須要、是否 能夠更新UI 【 後面會提一下這個循環時間問題 】
4.更新UI,UI rendering,同時阻塞 javascript 執行。而且繼續重複第一步。
以上即是一整個 event loop 流程,從流程中咱們能夠看到有倆個「任務隊列」,這倆個隊列實例化到 javascript 中的API 即是
Macrotask queue --> setTimeout || setInterval || javascript代碼 Microtask queue --> Promise.then()
至此一個完整的 event loop 流程便徹底說完了。
3.實例解析
什麼鬼?這麼複雜? 弄懂?不存在的
如今回到剛纔提到的 「老生常談的問題」 從實例的角度來講明一下問題。咱們假設這個 javascript 文件叫作 "main.js"
"main.js"中的代碼(+ 爲自定義標記)
+1 console.log(1); +2 setTimeout(e=>{ console.log(2); },0) +3 setTimeout(e=>{ console.log(3); },0) +4 new Promise((resolve,reject)=>{ console.log(4); resolve();}) .then(e=>{ console.log(5); }) +5 setTimeout(e=>{ console.log(6); +6 new Promise((resolve,reject)=>{ console.log(7); resolve(); }) .then(e=>{ console.log(8);}) })
那麼這個執行順序是怎樣呢?從頭帶尾梳理一遍(詞窮,全文只要是流程統一是「從頭至尾梳理一遍」)
macrotask: javascript 代碼,全部同步代碼執行。輸出: 1 4。註冊 +4 到 microtask。 註冊+2 +3 +5 到 macrotask。
microtask: 執行 +4 輸出: 5。
macrotask: 執行 +2。 輸出 2。
microtask: 無
macrotask: 執行 +3。 輸出 3。
microtask: 無
macrotask: 執行 +5。 輸出 6 7。 註冊 +6 到 microtask。
microtask: 輸出 8。
因此整體輸出的順序爲:1 4 5 2 3 6 7 8
若是這個輸出與你所想相同,那麼基本就沒有問題了。
那麼若是不對或者有問題怎麼辦?
PS: 前面提到 【本次循環耗時】這個問題,這裏我也不是很是清楚,望大牛指點。瀏覽器通常渲染頁面60/S,以達到每秒60幀(60 fps),因此大概16ms一次,既然有了時間咱們不經就會問?前面的任務處理耽誤了則麼辦?由於javascript線程與UI線程互斥,某些任務致使 javascript引擎 坑了隊友,天然而然無法在16ms的節點上到達這一步,從secrets of javascript ninja中瞭解到,通常會摒棄此次渲染,等待下一次循環。( 若有問題請指正! )
瀏覽器中的 event loop 到此結束,下面說說 NodeJS 的 event loop
二 NodeJS的event loop
NodeJS 的 event loop 也是有 Macrotask queue 與 Microtask queue 的。只不過 NodeJS 的略有不一樣。那麼主要說說不一樣在哪裏。
NodeJS中 Macrotask queue 與 Microtask queue 實例化到API爲: Macrotask queue --> script(主程序代碼),setImmediate, I/O,setTimeout, setInterval Microtask queue --> process.nextTick, Promise
1.Macrotask queue 不一樣之處
上面說到了瀏覽器 event loop 的 Macrotask queue 在每次循環中只會讀取一個任務,NodeJS 中 Macrotask queue 會一次性讀取完畢( 同階段的執行完畢,後面會說到Macrotask queue 分爲 6個階段 ),而後向下讀取Microtask。
注意: 這一條與 NodeJS版本有很大關係,在看 深刻淺出NodeJS 這一本書時( 看的版本很舊,不知是否有修訂版,若有請告知。 ),提到的 setImmediate 每次循環只會執行一次,而且給出的示例在 v8.9.1 版本跑時已不符合書中所寫。書中示例以下(+ 爲自定義標記,原文中沒有):
+1 process.nextTick(function () { console.log('nextTick執行1'); }); +2 process.nextTick(function () { console.log('nextTick執行2'); }); +3 setImmediate(function () { console.log('setImmediateჽ執行1'); +4 process.nextTick(function () { console.log('強勢插入'); }); }); +5 setImmediate(function () { console.log('setImmediateჽ執行2'); }); +6 console.log('正常執行'); 正常執行 nextTick執行1 nextTick執行2 setImmediate執行1 強勢插入 setImmediateჽ執行2
在 v8.9.1 中截圖以下
從圖片中能夠看到,至少在 v8.9.1 版本中 Macrotask queue 會直接所有執行。按照慣例從頭至尾的梳理一遍:
macrotask: javascript 代碼,全部同步代碼執行。輸出: 正常執行。註冊 +3 +5 到 Macrotask。執行process.nextTick(),最終輸出: 正常執行, nextTick執行1, nextTick執行2。
**microtask: 無
macrotask: 執行 +3 +5。 輸出: setImmediate執行1, setImmediateჽ執行2。 執行process.nextTick(),最終輸出: setImmediate執行1, setImmediateჽ執行2,強勢插入。
microtask: 無
因此最終輸出爲:正常執行, nextTick執行1, nextTick執行2,setImmediate執行1, setImmediateჽ執行2,強勢插入。
2.process.nextTick(),setImmediates,以及event loop的6個階段
NodeJS 中 Macrotask queue會分爲 6 個階段,每一個階段的做用以下(process.nextTick()在6個階段結束的時候都會執行):
timers:執行setTimeout() 和 setInterval()中到期的callback。 I/O callbacks:上一輪循環中有少數的I/Ocallback會被延遲到這一輪的這一階段執行 idle, prepare:僅內部使用 poll:最爲重要的階段,執行I/O callback,在適當的條件下會阻塞在這個階段 check:執行setImmediate的callback close callbacks:執行close事件的callback,例如socket.on("close",func)
注:此6個階段非筆者原創來自 https://cnodejs.org/topic/5a9...,文章從底層C代碼分析NodeJS event loop。這裏作只作簡單整合。侵刪。
在瞭解了這六個階段後,咱們能夠發現定時器系列在NodeJS event loop中 Macrotask queue 讀取順序爲:
1. setTimeout(fun,0) setInterval(fun,0) 2. setImmediate
空口無憑,在實例中瞭解。的代碼奉上( 代碼較長,分爲三段,方便閱讀,避免滾動。 ):
+1 process.nextTick(function(){ console.log("1"); }); +2 process.nextTick(function(){ console.log("2"); +3 setImmediate(function(){ console.log("3"); }); +4 process.nextTick(function(){ console.log("4"); }); }); +5 setImmediate(function(){ console.log("5"); +6 process.nextTick(function(){ console.log("6"); }); +7 setImmediate(function(){ console.log("7"); }); });
+8 setTimeout(e=>{ console.log(8); +9 new Promise((resolve,reject)=>{ console.log(8+"promise"); resolve(); }).then(e=>{ console.log(8+"promise+then"); }) },0) +10 setTimeout(e=>{ console.log(9); },0) +11 setImmediate(function(){ console.log("10"); +12 process.nextTick(function(){ console.log("11"); }); +13 process.nextTick(function(){ console.log("12"); }); +14 setImmediate(function(){ console.log("13"); }); });
console.log("14"); +15 new Promise((resolve,reject)=>{ console.log(15); resolve(); }).then(e=>{ console.log(16); })
這麼複雜的異步嵌套在一塊兒是否是很頭疼呢?
我!不!看!了!
最後一遍梳理,最多、最全的一次梳理。自古以來從頭至尾的梳理一遍
macrotask: javascript 代碼,全部同步代碼執行。輸出: 14。執行process.nextTick(),最終輸出: 14,15, 1, 2, 4。 註冊 +3 +5 +8 +11 到 Macrotask。 註冊 +15 到 Microtask。
microtask: 執行 +15 輸出 16
macrotask: 執行 +8 +10 輸出 8, 8promise, 9。 註冊 +9 到 Microtask。
microtask: 執行 +9 輸出 8promise+then
macrotask: 執行 +5 +11 +3 輸出 5, 10, 3。 註冊 +7 +14 到 macrotask。執行process.nextTick(),最終輸出: 5 10 3 6 11 12。
microtask: 無
macrotask: 執行 +7 +14。 輸出: 7,13
microtask: 無
由此最中所有的輸出爲:14,15,1,2,4,8,8promise,9,8promise+then,5,10,3,6,11,12,7,13
三 結束
到此結束了。瀏覽器的、NodeJS 的 event loop 已所有分析完成,過程當中引用:阮一峯博客,知乎,CSDN部分文章內容,侵刪。
最近在瞭解部分底層知識,收穫頗豐。其中包括 for of.... 等等各類奇奇怪怪的問題,有時間再寫吧。
最後,本人菜鳥,若有不對、不實、誤導等錯誤、問題,歡迎評論區指正。