摘要:瀏覽器與的異同,以及部分機制有人對部分迷惑,本身構造函數是同步的,是異步。瀏覽器的的已全部分析完成,過程中引用阮一峰博客,知乎,部分文章內容,侵刪。
瀏覽器與NodeJS的EventLoop異同,以及部分機制
PS:有人對promise部分迷惑,Promise本身構造函數是同步的,.then是異步。---- 2018/7/6 22:35修改
javascript 是一門單線程的腳本語言,雖然是單線程但是有很多異步的API來幫助開發者解決線程的阻塞問題。比如:onClick 注冊的回調函數、必不可少的ajax等等...但是 javascript 運行環境是如何做到單線程卻又不是一直阻塞線程等待各種異步操作完成才繼續執行操作的呢?
答案就是: event loop
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 loop
1.簡單了解
event loop 即事件循環,它到底是什么結構呢? 阮一峰老師的博客有一張圖,雖然很直白、明了但是少了一些東西不能全面的將 event loop 整體循環機制展示出來。先來看圖:
圖片非筆者原創,來自阮一峰博客,在此說明,侵刪。
從圖中我們可以得到信息是:
1.javascript 引擎執行 javascript 是單線程的,因為只有一個 stack 里面有各種正在執行、等待執行的事件。
2.有一些 webAPI 將執行時產生的 callback 放入一個隊列,即 “事件隊列”。
3.在event loop 循環中不停的將“事件隊列”里等待執行的事件,推入 javascript 執行棧。
這就是事件循環簡化的機制,為什么說簡化呢?因為在循環中還做了很多沒有提及的操作、規則。
我就不舉栗子了,但是我要打個比方。
就說一個老生常談的問題 (文章編輯不便,直接一行了,換行黨你倒是來打我啊!)
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 中任務。有倆種情況
任務隊列空,向下執行
任務隊列不為空,將最先進入的一個(手動+文章加粗)任務推入 javascript 執行棧,向下執行
2.讀取 Microtask queue 中任務。有倆種情況
任務隊列空,向下執行
任務隊列不為空,將最先進入的一個任務推入 javascript 執行棧,并且再次重復此操作(手動+文章加粗),直到 Microtask queue 為空。直白的說:將此任務隊列按照先后順序將所有任務推入javascript 執行棧,向下執行
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.... 等等各種奇奇怪怪的問題,有時間再寫吧。
最后,本人菜鳥,如有不對、不實、誤導等錯誤、問題,歡迎評論區指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95971.html
摘要:在他的重學前端課程中提到到現在為止,前端工程師已經成為研發體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發是一個非常特殊的行業,它的歷史實際上不是很長,但是知識之繁雜,技術迭代速度之快是其他技術所不能比擬的。 winter在他的《重學前端》課程中提到: 到現在為止,前端工程師已經成為研...
摘要:在他的重學前端課程中提到到現在為止,前端工程師已經成為研發體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學習。一基礎前端工程師吃飯的家伙,深度廣度一樣都不能差。開篇 前端開發是一個非常特殊的行業,它的歷史實際上不是很長,但是知識之繁雜,技術迭代速度之快是其他技術所不能比擬的。 winter在他的《重學前端》課程中提到: 到現在為止,前端工程師已經成為研發體系...
摘要:下面我們說一下前端的和。現在就可以知道了,前端的其實是由組合而成。這么一對比,相信很多小伙伴對更加了解了,原來前端和服務端的如此相似,他們的基礎是相同的,只是環境不同,導致他們擴展出來的東西不同而已。 前言 很多小伙伴學Node的時候,都沒有好好認識她就開始瘋狂追求,想一舉拿下,直接在網上搜索Node實戰,想知道她活好不好,想先用她建個簡單博客練練手。 JavaScript和Nodej...
閱讀 3558·2021-11-22 15:11
閱讀 4633·2021-11-18 13:15
閱讀 2702·2019-08-29 14:08
閱讀 3576·2019-08-26 13:49
閱讀 3090·2019-08-26 12:17
閱讀 3287·2019-08-26 11:54
閱讀 3110·2019-08-26 10:58
閱讀 2030·2019-08-26 10:21