梳理vue雙向綁定的實現原理

Vue 採用數據劫持結合發佈者-訂閱者模式的方式來實現數據的響應式,經過Object.defineProperty來劫持數據的setter,getter,在數據變更時發佈消息給訂閱者,訂閱者收到消息後進行相應的處理。html

 

要實現mvvm的雙向綁定,就必需要實現如下幾點:vue

  1. Compile—指令解析系統,對每一個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數node

  2. Observer—數據監聽系統,可以對數據對象的全部屬性進行監聽,若有變更可拿到最新值並通知訂閱者react

  3. Dep+Watcher—發佈訂閱模型,做爲鏈接Observer和Compile的橋樑,可以訂閱並收到每一個屬性變更的通知,執行指令綁定的相應回調函數,從而更新視圖。git

    Dep是發佈訂閱者模型中的發佈者:get數據的時候,收集訂閱者,觸發Watcher的依賴收集;set數據時發佈更新,通知Watcher 。一個Dep實例對應一個對象屬性或一個被觀察的對象,用來收集訂閱者和在數據改變時,發佈更新。github

     

    Watcher是發佈訂閱者模型中的訂閱者:訂閱的數據改變時執行相應的回調函數(更新視圖或表達式的值)。一個Watcher能夠更新視圖,如html模板中用到的{{test}},也能夠執行一個$watch監督的表達式的回調函數(Vue實例中的watch項底層是調用的$watch實現的),還能夠更新一個計算屬性(即Vue實例中的computed項)。web

mvvm入口函數,整合以上三者,具體如圖所示:算法

Untitled-1.jpg

 

compire能夠參看《雙向綁定的實現原理》,這裏不作過多解讀。api

Observer,Dep和Watcher類的實現及原理,推薦閱讀《Vue源碼解讀一:Vue數據響應式原理》,通常開發者須要關注:數組

收集依賴指的是誰收集依賴,依賴又是指的什麼?

Watcher,做用是分割表達式,收集依賴而且在值變化的時候調用回調函數。

咱們上面說過一個Dep對應着一個數據(這個數據多是:對象的屬性、一個對象、一個數組);一個Watcher對應能夠是一個模板也能夠是一個$watch對應的表達式、函數等,不管那種狀況,他們都依賴於data裏面的數據,因此這裏說的依賴其實就是模板或表達式所依賴的數據,對應着相關數據的Dep。

Watcher的四個使用場景

  • 第一種:觀察模板中的數據

  • 第二種:觀察建立Vue實例時watch選項裏的數據

  • 第三種:觀察建立Vue實例時computed選項裏的數據所依賴的數據

  • 第四種:調用$watch api觀察的數據或表達式

Watcher只有在這四種場景中,Watcher纔會收集依賴,更新模板或表達式,不然,數據改變後,沒法通知依賴這個數據的模板或表達式:

因此在解決數據改變,模板或表達式沒有改變的問題時,能夠這麼作:

首先仔細看一看數據是否在上述四種應用場景中,以便確認數據已經收集依賴;其次查看改變數據的方式,肯定這種方式會使數據的改變被攔截(關於這一點,上面Obsever相關內容中說的比較多)。

對於Observer須要注意的是:

20181026182644699835594.jpeg

getter/setter方法攔截數據的不足

    1. 當對象增刪的時候,是監控不到的。好比:data={a:"a"},這個時候若是咱們設置data.test="test",這個時候是監控不到的。由於在observe data的時候,會遍歷已有的每一個屬性(好比a),添加getter/setter,然後面設置的test屬性並無機會設置getter/setter,因此檢測不到變化。一樣的,刪除對象屬性的時候,getter/setter會跟着屬性一塊兒被刪除掉,攔截不到變化。

      vm.$set/Vue.set和vm.$delete/Vue.delete這樣的api來解決這個問題

    2. getter/setter是針對對象的對於數組的修改(push(),pop(),shift(),unshift(),splice(),sort(),reverse())等方法,arr發生了改變,此時是須要更新視圖的,可是arr的getter/setter攔截不到變化(只有在賦值的時候纔會調用setter,好比:arr=[6,7,8])。

      對於這種狀況,vue經過改寫Array的默認方法,在調用這些方法的時候發佈更新消息。通常無需關注,可是對於以下兩種狀況:

      1. 當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue

      2. 當你修改數組的長度時,例如:vm.items.length = newLength

須要vm.$set/Vue.set和vm.items.splice(newLength)解決,具體參看官方說明

  1. 每次給數據設置值得時候,都會調用setter函數,這個時候就會發布屬性更新消息,即便數據的值沒有變。從性能方便考慮咱們確定但願值沒有變化的時候,不更新模板。(像Angular這樣把批量操做延時到一次更新,一次作完全部數據變動,而後總體應用到界面上)

5246378-31f938950d89c8aa.jpg

總體感知virtual DOM

virtual DOM分爲三個步驟:

1.createElement(): 用 JavaScript對象(虛擬樹) 描述 真實DOM對象(真實樹)

2.diff(oldNode, newNode) : 對比新舊兩個虛擬樹的區別,收集差別

3.patch() : 將差別應用到真實DOM樹

 

有的時候 第二步 可能與 第三步 合併成一步(Vue 中的patch就是這樣)

 

Vue的實現原理總結

  1. 首先,在實例化的過程當中,把一個普通 JavaScript 對象傳給 Vue 實例的 data選項,Vue 將遍歷此對象全部的屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter。

  2. Dep 是一個依賴收集器。data 下的每個屬性都有一個惟一的 Dep 對象,在 get 中收集僅針對該屬性的依賴,而後在 set 方法中觸發全部收集的依賴。

  3. 在Watcher中對錶達式求值,從而觸發數據的get。在求值以前將當前Watch實例設置到全局,使用pushTarget(this)方法。

  4. 在get()中收集依賴,this.subs.push(sub),set的時候觸發回調Dep.notify()。

  5. Compile中首先將template或el編譯成render函數,render函數返回一個虛擬DOM對象(將模板轉爲 render 函數的時候,實際是先生成的抽象語法樹(AST),再將抽象語法樹轉成的 render 函數)

  6. 當 vm._render 執行的時候,所依賴的變量就會被求值,並被收集爲依賴。按照Vue中 watcher.js 的邏輯,當依賴的變量有變化時不只僅回調函數被執行,實際上還要從新求值,即還要執行一遍

  7. 若是尚未 prevVnode 說明是首次渲染,直接建立真實DOM。若是已經有了 prevVnode 說明不是首次渲染,那麼就採用 patch 算法進行必要的DOM操做。這就是Vue更新DOM的邏輯。

最後,安利下:《Vue.js 技術揭祕

參考文章

梳理Vue2.0雙向綁定的實現原理

文自《梳理vue雙向綁定的實現原理 - vue入坑總結 - 周陸軍的我的網站》,若有不妥以前,請源站留言告知。

相關文章
相關標籤/搜索