event loop 與 vue

結論

對於event loop 能夠抽象成一段簡單的代碼表示javascript

for (macroTask of macroTaskQueue) {
    // 1. Handle current MACRO-TASK
    handleMacroTask();
      
    // 2. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

js事件機制

javascript是一個單線程語言,同一時間只能執行一個任務。
對於javascript的事件處理機制,咱們能夠簡單理解成「主線程+任務隊列」模式。主要步驟以下html

(1)全部同步任務都在主線程上執行,造成一個執行棧。

(2)主線程以外,還存在一個 "任務隊列"(task queue)。只要異步任務有了運行結果,就在 "任務隊列" 之中放置一個事件。
(3)一旦 "執行棧" 中的全部同步任務執行完畢,系統就會讀取 "任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。java

(4)主線程不斷重複上面的第三步。web

任務隊列

任務隊列分爲task queue和microtask queue。執行棧任務清空後會先從Microtasks中取任務,Microtasks中執行完以後纔會執行task中的任務。segmentfault

所以一個event loop主要流程以下:api

  1. 開始一個Event loop
  2. 執行棧從tasks queue中取任務,並執行。
  3. 執行完後,執行棧清空
  4. 執行棧從microtasks queue中取任務執行
  5. 執行完成,執行棧清空
  6. 判斷microtasks queue是否還有任務,有則重複步驟3。
  7. 進入 Update the rendering(更新渲染)階段
  8. Event loop結束

流程圖以下:瀏覽器

再仔細理解一下開頭列出的代碼:網絡

for (macroTask of macroTaskQueue) {
    // 1. Handle current MACRO-TASK
    handleMacroTask();
      
    // 2. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}

VUE與EVENT_LOOP

爲何使用Microtask queue

對於VUE這類web2.0框架而言,最主要作的應該仍是把對data的修改映射到DOM上。若是隻修改一個數據,就刷新一次DOM,那麼在一個同步過程當中,同時修改好幾個數據,必然會致使屢次渲染。這確定是不可取的。
從上面的event loop咱們瞭解到,一個event loop對應一次render。理想情況固然就是,一次event loop產生的全部改動最好再render以前將DOM都先更新好。這樣在一個週期中就能夠只render一次。app

從這點需求出發很容易發現,microtask queue很符合要求。框架

  1. microtask queue確定在render ui以前執行完
  2. 不像task queue存在多個,microtask只存在一個queue。
  3. 最關鍵的是: microtask queue只有都清空了才能進入下一步,不管queue裏是何時塞進來的。

實際代碼中,不論是鼠標點擊仍是鍵盤輸入或是網絡時間,觸發了哪些方法,這些觸發均可以當作開啓一個event loop。這些觸發形成的任何修改都放到microtask queue中,就能夠保證在這一輪的evnet loop走到render ui時能夠拿到最新的DOM。

要說明的是,這裏的render並非維護虛擬DOM,也不是把虛擬DOM的變化投射到真實DOM上。而是將真實DOM更新到UI的過程。

這麼說是由於:Event Loop 並非在 ECMAScript 標準中定義的,而是在 HTML 標準中定義的:

To coordinate events, user interaction, scripts, rendering, networking, and so forth...

在 JavaScript Engine 中(以 V8 爲例),只是實現了 ECMAScript 標準,而並不關心什麼 Event Loop。也就是說 Event Loop 是屬於 JavaScript Runtime 的,是由宿主環境提供的(好比瀏覽器)。
瀏覽器可不會關心什麼虛擬DOM。只負責DOM改變後渲染UI。

爲何不用task queue

同上: 在開啓一個event loop後,若是將任務放到task queue中,那麼這個task任務只會在本輪Event loop結束後纔會執行,並開啓新一輪event loop。這無疑會致使兩次render UI。
實際上,尤大爲了修復一些bug,曾經將VUE.nexttick用task queue實現。可是致使了很明顯的性能問題。
能夠看看兩個列子: 例一 , 例二
兩個fiddle的實現如出一轍,就是讓那個絕對定位的黃色元素起到一個fixed定位的效果:綁定scroll事件,每次滾動的時候,計算當前滾動的位置並更改到那個絕對定位元素的top屬性上去。你們本身試試滾動幾下,對比下效果,你就會發現第一個fiddle中的黃元素是穩定不動的,fixed很好。然後一個fiddle中就有問題了,黃色元素上下晃動,彷佛跟不上咱們scroll的節奏,總要慢一點,雖然最後停下滾動時位置是對的。

上述兩個例子實際上是在這個issue中找到的,第一個jsfiddle使用的版本是Vue 2.0.0-rc.6,這個版本的nextTick實現是採用了MO,然後由於IOS9.3的WebView裏的MO有bug,因而尤雨溪更改了實現,換成了window.postMessage,也就是後一個fiddle所使用的Vue 2.0.0-rc.7。後來尤雨溪瞭解到window.postMessage是將回調放入的macrotask 隊列。這就是問題的根源了。

參考:

  1. Tasks, microtasks, queues and schedules
  2. 深刻理解 JavaScript Event Loop
  3. Vue源碼詳解之nextTick:MutationObserver只是浮雲,microtask纔是核心!

來源:http://www.javashuo.com/article/p-qbcsjuis-cz.html

相關文章
相關標籤/搜索