1. 程式人生 > >總結javascript基礎概念(二):事件隊列循環

總結javascript基礎概念(二):事件隊列循環

數量 次循環 下一個 es6 類型 同步 函數 ref rip

主要問題:

  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》

 

總結javascript基礎概念(二):事件隊列循環