理清Vue響應式系統中的Watcher和Dep的關係

理清Vue響應式系統中的Watcher

[TOC]算法

引言

在這裏我先提出兩個問題(文章末尾會進行解答):數組

  • Vue的數據響應系統中,DepWatcher各自分擔什麼任務?
  • Vue的數據響應系統的核心是Object.defineproperty必定是最好的嗎?有什麼弊端和漏洞嗎?

1、什麼是響應系統中的Watcher,它的做用是什麼?

響應系統中的Watcher即這個系統的觀察者,它是響應系統中觀察者模式的載體,當響應系統中的數據發生改變的時候,它可以知道而且執行相應的函數以達到某種業務邏輯的目的。打個比方,若是你是一個商家,要寄一批貨分別給不一樣的客戶,那麼watcher就是一個個快遞員,發出的動做就是數據發生改變。你只須要負責寄出去這個動做就好了,如何找到、送到客戶則是watcher的事情。性能優化

每一個watcher和數據之間的關係要麼是1對1,要麼是多對多關係(這與watcher的類型有關),watcher和業務邏輯只有1對1關係。函數

2、Watcher的類型

Vue源碼中是沒有體現出Watcher的類型的,我在這裏給Watcher添加類型是爲了更好地理解Watcher這個對象。Watcher在普通的業務邏輯上能夠分爲如下三類:post

  • 普通的Watcher:與數據1對1關係。
  • lazyWatcher:與數據1對1關係,可是它是一個惰性求值的觀察者,怎麼體現呢?對它進行賦值是不會改變它的值,只有當獲取它的值的時候,纔會更新最新版的數據(在Vue中體現爲computed方法,通常求值是經過方法來求值的)。
  • renderWatcher:與數據是1對多(不考慮傳參進子組件)的關係,在一個組件中,渲染函數觀察者必定是最後生成的,因此執行觀察者隊列的時候,渲染函數觀察者在一個組件中是最後執行的。

在這裏多嘴一下lazy型的觀察者是怎麼回事吧。lazy型觀察者在Vue中表現爲computed屬性,通常這個屬性是一個函數,如下是一個例子:性能

computed: {
  // getCount最後處理成一個屬性,而後這個方法被存儲在Watcher的某個屬性中
  getCount() {
    return this.a + this.b;
  }
}
複製代碼

lazy觀察者裏面有一個dirty屬性,也就是一個開關做用,只有它爲true的時候使用getCountgetter方法的時候,纔會進行調用這個函數。優化

若是lazy觀察者所引用的數據(a或者b屬性)發生改變後,會將這個放到觀察者隊列中,而後執行這個觀察者的時候把dirty賦值爲true(表明下次訪問getter方法的時候會執行一遍lazy的求值方法(求值後會將dirty賦值爲false))。等到下一次須要獲取這個數據的時候才進行求值,因此它叫作惰性求值。這種方式可以節省沒必要要執行函數的開支。this

3、Watcher和Dep的關係

看過Vue源碼的defineReactive這個方法,就會發現一個被觀察的對象裏面每一個屬性會有一個Dep依賴筐來存放全部觀察它的Watcher。而defineReactive方法只有初始化每一個屬性的dep卻並無建立觀察者(要分清初始化和建立觀察者是分開這個事實)。那麼這個Dep如何添加觀察者呢?Vue使用了全局變量,這個變量叫作Dep.target,它是一個Watcher類型的變量,來將WatcherDep進行互相綁定。數據的綁定用圖來表示的話以下:spa

咱們能夠明確如下區別:代理

  • $watch方法定義的觀察者,若是不設定immediate屬性,那麼是不會進行調用的,而computedrender是會進行調用方法的。
  • 數據的Depsubs數組存放這個數據所綁定的觀察者對象,觀察者對象的deps數組中存放着與這個觀察者有關的數據Dep。因此數據的DepWatcher實際上是多對多關係
  • $watchcomputed觀察者是在created生命鉤子函數前就建立完畢而且綁定的,而render觀察者是在mounted以前建立並綁定的,因此同一個組件中,render觀察者的id會大於其餘觀察者(id是在後面執行隊列裏面升序排序的時候的依據)。 換句話說,在同一個組件的觀察者中,當數據發生改變的時候,渲染函數觀察者必定是最後執行的。 這個很好理解,其餘觀察者中不免會對數據進行修改,若是渲染函數觀察者先執行了,而後其餘觀察者對數據進行改變的話,那麼沒辦法將數據準確呈如今頁面上,致使數據不一致性。

4、講一下觀察者執行隊列機制

Vue是如何實現性能優化的呢?最顯著的兩個點:

  • 觀察者設定執行隊列,批量執行。
  • diff算法減小渲染開支。

第二個不在這裏面講解,咱們看一下第一個是怎麼回事?

這個隊列的長度是怎麼定量的呢?

  • 最大長度是100,源碼擺在那裏。

  • 以一個事件循環時間段爲蒐集時間。(什麼是事件循環?能夠看一下本博客系統的其餘優秀文章)

它的流程是以下的:

  • 未執行時候:若是有更改過數據,那麼就將對應的觀察者直接推動隊列中(執行的時候會進行根據id升序排序後執行)
  • 在執行中的時候,若是有新的觀察者進來了(觀察者中更改數據,而後這個數據又綁定觀察者),按照序號升序來進行排序。

5、解答前面的問題

  1. Dep是負責存放數據所綁定全部的觀察者的對象的容器,只要數據發生改變,就會經過這個Dep來通知全部觀察者進行修改數據。(每一個數據都有獨一無二的Dep)。而Watcher更偏向於一個動做,也就是規定的業務邏輯或者渲染函數,是一個執行者。
  2. ES5是很輕便的,很好的。可是在ES6出現後,它就必定不是最好的,由於ES6有一個Proxy代理來統一進行處理。(ES6使用代理實現Vue數據響應系統(TypeScript)
    • 弊端:若是一個數據有1000個屬性,那麼就要給這1000個屬性使用Object.defineProperty,這樣在初始化頁面的時候會形成卡頓。若是用代理的話,那麼只須要執行一次就能夠了。
    • 漏洞:若是咱們拿到了vm實例,那麼用戶是能夠在運行的時候經過修改對象屬性的描述符(descriptor)來進行修改它,會形成系統的不肯定性。這是由於響應系統的模式致使必須將數據的描述符的configuration設爲true,因此在運行的時候可以對它進行修改。

這個問答是我我的閱讀完代碼後的解答,若是有什麼回答錯的請指正。

相關文章
相關標籤/搜索