Vue2 雙向綁定原理(數據響應式)

目錄

1. MVVM

2. Vue2 雙向綁定原理(數據響應式)

(一)MVVM

1. 定義:

MVVM 是 Model-View-ViewModel(模型-視圖-視圖模型)的縮寫;是一種軟件架構設計模式。

其中:

  • Model:是數據模型,既後端傳過來的數據;
  • View:是視圖層,既我們看到的頁面;
  • ViewModel:是連接Model和View的中間橋樑

在MVVM的框架下,View和Model是不能直接通信的,Model與View之間通過ViewMode關聯。ViewModel負責將Model數據的變化顯示到View上,將View的改變反饋到Model上。這就是我們常說的雙向綁定數據機制。

2. 優點:

  • 降低耦合:視圖(View)可以獨立於Model變化和修改,一個ViewModel可以綁定到不同的"View"上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。
  • 可重用性:可以把一些視圖邏輯放在ViewModel層中,讓很多View重用這些視圖邏輯。
  • 獨立開發。開發人員可以專注於業務邏輯和數據的開發(ViewModel),設計人員可以專注於頁面設計。
  • 可測試。界面素來是比較難於測試的,測試可以針對ViewModel來寫。

3. 缺點:

  • 數據綁定使得 Bug 很難被調試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。
  • 因爲使用了dataBinding,增加了大量的內存開銷,增加了程序的編譯時間,項目越大內存開銷越大。

那如何設計MVVM模型模型呢。需要解決兩個關鍵問題:

  • 如何知道數據更新。
  • 數據更新後,如何通知變化。

下面就分別介紹vue是如何實現的以上兩點,理解了這兩點,基本上也就明白了雙向綁定的機制。

(二)Vue2 雙向綁定原理(數據響應式)

1. 原理概述

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

要實現MVVM的雙向綁定,就必須要實現以下幾點:

  • Compile—指令解析系統,對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
  • Observer—數據的觀察者,讓數據對象的讀寫操作都處於自己的監管之下。當初始化實例的時候,會遞歸遍歷data,用Object.defineProperty來攔截數據(包含數組裏的每個數據)。
  • Dep+Watcher—發佈訂閱模型,作爲連接Observer和Compile的橋樑,能夠訂閱並收到每個屬性變動的通知,執行指令綁定的相應回調函數,從而更新視圖。

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

在這裏插入圖片描述

下面分別說明數據劫持與發佈者-訂閱者模式。

2. 數據劫持

Object.defineProperty()

在ES5中有Object.defineProperty()方法,它能監聽各個屬性的set和get方法。

Object.defineProperty()方法,有三個參數,分別爲待監聽的數據對象,待監聽的屬性,以及對set,get的監聽方法。

例如:對data對象的name屬性進行監聽,當執行"data.name='fyn'"觸發set方法,執行"data.name"觸發get方法。

 let data ={name:'tcy'}
  Object.defineProperty(data,'name',{
  	set: function(newValue) {
        console.log('更新了data的name:' + newValue);
    },
    get: function() {
        console.log('獲取data數據name');
    }
  })
data.name="fyn";//更新了data的name:fyn
data.name;//獲取data數據name

什麼是數據劫持

通過上面對Object.defineProperty的介紹,我們不難發現,當我們訪問或設置對象的屬性的時候,都會觸發相對應的函數,然後在這個函數裏返回或設置屬性的值。

既然如此,我們當然可以在觸發函數的時候動一些手腳做點我們自己想做的事情,這也就是“劫持”操作。

模擬Vue實現數據劫持

vue正是採用了Object.defineProperty()方法,對data的屬性進行劫持。我們來模擬實現其劫持的過程。

/*
模擬vue的data數據
 var vm = new Vue(
{
   data:{
       name:'tcy',
       age:'20'
   }
 }
 	)
 */
let data ={name:'tcy',age:'20'}
function observe(data){
	//獲取所有的data數據對象中的所有屬性進行遍歷
    const keys = Object.keys(data)
    for (let i = 0; i < keys.length; i++) {
    	let val = data[keys[i]];
       defineReactive(data, keys[i],val)//爲每個屬性增加監聽
    }
}
function defineReactive(obj,key,val){
   Object.defineProperty(obj, key, {
    enumerable: true,//可枚舉
    configurable: true,//可配置
    get: function reactiveGetter () {
      //模擬get劫持
      console.log("get劫持");
      return val;
    },
    set: function reactiveSetter (newVal) {
       	//模擬set劫持
     console.log("set劫持,新值:"+newVal);
     val = newVal;
    }
  })
}
observe(data);
data.name="fyn";//set劫持,新值:fyn
console.log(data.name);//get劫持,fyn

data 模擬 vue.data 對象,observer 中對 data 的屬性進行遍歷,調用 defineReactive 對每個屬性的 get 和 set 方法進行劫持。

由此,data數據的任何屬性值變化,都可以監聽和劫持,上述的第一個問題(如何知道數據更新)就解決了。那view端的數據變化是如何知道的呢,view端改變數據的組件無外乎input,select等,可以用組件的 onchange 事件監聽,這裏就不再重點描述。

3. 發佈與訂閱

vue在雙向綁定的設計中,採用的是觀察-訂閱模式,前面所講的數據劫持,其實就是爲屬性創建了一個觀察者對象,監聽數據的變化。接下來就是創建發佈類和訂閱類。

來看一下下面的圖:

在這裏插入圖片描述

圖中:

  • observer,創建數據監聽,併爲每個屬性建立一個發佈類。

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

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

其中:

  • 圖中紅色的箭頭表示的是收集依賴時獲取數據的流程。Watcher會收集依賴的時候(這個時機可能是實例創建時,解析模板、初始化watch、初始化computed,也可能是數據改變後,Watcher執行回調函數前),會獲取數據的值,此時Observer會攔截數據(即調用get函數),然後通知Dep可以收集訂閱者啦。Dep將訂閱數據的Watcher保存下來,便於後面通知更新。

  • 圖中綠色的箭頭表示的是數據改變時,發佈更新的流程。當數據改變時,即設置數據時,此時Observer會攔截數據(即調用set函數),然後通知Dep,數據改變了,此時Dep通知Watcher,可以更新視圖啦。

具體過程:

VUE探索第一篇–雙向綁定原理

Vue源碼解讀一:Vue數據響應式原理

學習參考於

VUE探索第一篇–雙向綁定原理

Vue源碼解讀一:Vue數據響應式原理

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章