主要問題:1、JS引擎是單線程,如何完成事件迴圈的?2、定時器函數為什麼計時不准確?3、回調與非同步,有什麼聯繫和不同?4、ES6的事件迴圈有什麼變化?Node中呢?5、非同步控制有什麼難點?有什麼解決方案? ...
主要問題:
1、JS引擎是單線程,如何完成事件迴圈的?
2、定時器函數為什麼計時不准確?
3、回調與非同步,有什麼聯繫和不同?
4、ES6的事件迴圈有什麼變化?Node中呢?
5、非同步控制有什麼難點?有什麼解決方案?
二、事件隊列迴圈
(一)、瀏覽器線程
JavaScript引擎是基於事件驅動單線程運行的,瀏覽器無論在什麼時候都只且只有一個線程在運行JavaScript程式,等待著任務隊列中任務的到來,然後加以處理。
瀏覽器的內核是多線程的,它們在內核控制下相互配合協作,一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程,GUI渲染線程,瀏覽器事件線程。
GUI渲染線程負責渲染瀏覽器界面,當界面需要重繪(Repaint)或由於某種操作引發迴流(Reflow)時,該線程就會執行。GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閑時立即被執行。所以渲染操作的消耗特別大。
(二)、事件類型與隊列
事件迴圈:引擎會創建一個類似於 while (true) 的無限迴圈,每執行一次迴圈體的過程稱之為 Tick。每次 Tick 的過程就是查看是否有待處理事件,如果有則取出相關事件及回調函數放入執行棧中由主線程執行。待處理的事件會存儲在一個任務隊列中,也就是每次 Tick 會查看任務隊列中是否有需要執行的任務。
任務隊列:非同步操作會將相關回調添加到任務隊列中。而不同的非同步操作添加到任務隊列的時機也不同,如 onclick, setTimeout, ajax 處理的方式都不同,這些非同步操作是由瀏覽器內核的 webcore 來執行的,webcore 包含上圖中的3種 webAPI,分別是 DOM Binding、network、timer模塊。
onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會立即添加到任務隊列中。
setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,才會將回調函數添加到任務隊列中。
js定時器不准確的原因:
1、非同步函數的回調執行會阻塞下一個迴圈tick。2、瀏覽器的時間精度不一。
ajax 則會由瀏覽器內核的 network 模塊來處理,在網路請求完成返回之後,才將回調添加到任務隊列中。
主線程:JS 只有一個線程,稱之為主線程。而事件迴圈是主線程中執行棧里的代碼執行完畢之後,才開始執行的。所以,主線程中要執行的代碼時間過長,會阻塞事件迴圈的執行,也就會阻塞非同步操作的執行。只有當主線程中執行棧為空的時候(即同步代碼執行完後),才會進行事件迴圈來觀察要執行的事件回調,當事件迴圈檢測到任務隊列中有事件就取出相關回調放入執行棧中由主線程執行。
在這個過程提高頁面響應速度的方式:減少主線程同步代碼的數量,將不重要的代碼轉移到事件迴圈階段執行。
(三)、ES6的任務隊列
對事件隊列進行了改造,使得非同步回調可以更早的執行,每個tick間隙都會優先執行任務隊列,不用排到事件隊列的末尾。一道必考題,看一下執行順序:
Promise實例具有三種狀態:等待,決議,拒絕。由此產生的非同步事件執行會作為任務隊列掛在當前tick迴圈的末尾執行。
(四)、Nodo中的事件迴圈
JS引擎的事件迴圈都需要宿主環境提供隊列維護,Node脫離了瀏覽器,用到了不同的方式和底層系統做交互。
觀察者:引擎在每個迴圈過程中詢問觀察者是否有要處理的事件。
在Window下,觀察者基於IOCP監聽事件的完成情況;在*nix下基於多線程創建。
在node增加了非同步執行的api,如process。nextTick()和setImmiediate。
process。nextTick()的回調函數保存在一個數組中,setImmiediate則是保存在一個鏈表中。
在每輪迴圈中,會將nextTick的數組中回調函數全部執行完,然後執行一個setImmiediate鏈表中的回調。
(五)、非同步事件的處理
非同步事件的處理過程中有執行時間、順序不確定,回調地獄,錯誤難捕獲定位等問題。
1、jQuery的 Deferred隊列模塊
JQ在 Deferred隊列模塊的基礎上,模擬實現了promise相似的方法。可以做到鏈式回調,延遲訪問。
2、ES6 Promise
promise實例決議後就不可更改,解決了信任問題。可以使用鏈式回調,隨時訪問非同步事件的狀態。相比之前要將回調交給另一方控制,promise可以將回調的執行控制在自己的邏輯中。至此仍未從實質上解決非同步。
3、Generator
生成器函數實現了真正的非同步控制,可以切換執行環境,併在執行環境之間傳遞變數,實現協作的函數線程。
著名的co模塊,結合使用Promise和Generator實現了非同步流程的同步編碼形式。
4、async 函數
ES2017 標準引入了 async 函數,使得非同步操作變得更加方便。就是 Generator 函數的語法糖。
Node框架Koa採用了最新的async處理非同步流程,使得編碼更加簡潔流暢。
推薦參考書:
《深入淺出NodeJS》
《jQuery技術內幕》
《Webkit技術內幕》
《你不知道的JavaScript》