實現雙向數據綁定

MVVM框架主要包含3個部分:model、viewviewmodel。

  1. Model:指的是數據部分,對應到前端就是javascript對象
  2. View:指的是視圖部分,對應前端就是dom
  3. Viewmodel:就是連接視圖與數據的中間件
1.雙向數據綁定的實現方式

簡單的來說,就是框架的控制器層(這里的控制器層是一個泛指,可以理解為控制view行為和聯系model層的中間件)和UI展示層(view層)建立一個雙向的數據通道。當這兩層中的任何一方發生變化時,另一層將會自動作出相應的變化。

vue-MVVM

一般來說要實現這種雙向數據綁定,在前端我目前了解的有三種形式:

  • 基于臟檢查 angular,regular(網易開發)
  • 觀察機制
  • 封裝屬性訪問器

2.基于臟檢查

目前angular,regular的實現都是基于臟檢查。當發生某些特定的事情的時候,框架會調用相關的digest方法。內部邏輯就是遍歷所有的watcher,對監控的屬性做對比。如果值發生了變化,則執行相應的handler。

2.1基于regular詳細介紹一下:
watcher對象看起來是這樣的:
{
  get: function(context){...}  //獲得表達式當前求值,此函數在解析時,已經生成
  set: function(){} // 有些表達式可以生成set函數,用于處理賦  值,這個一般用于雙向綁定的場景
  once: false // 此監聽器是否只生效一次
  last: undefined// 上一次表達式的求值結果
  fn: function(newvalue, oldvalue){} // 即你傳入$watch的第二個參數,當值改變時,會調用此函數
  // ...
}

當系統進入臟檢查階段,遍歷所有的$watch綁定的watcher,然后對比watcher.get()watcher.last,如果不同則運行對應的watcher.fn(newvalue, oldvalue)。然后再進入下一個watcher的檢查。

何時進行臟檢查?

在Regular中,digest階段是由$update
方法觸發的。
具體源碼在regular/src/helper/watcher.js里面

由于regularjs是基于臟檢查,所以當不是由regularjs本身控制的操作(如事件、指令)引起的數據操作,可能需要你手動的去同步data與view的數據. $update方法即幫助將你的data同步到view層.

//手動同步
var component = new Regular();
component.data.name = 'leeluolee'
// you need call $update to Synchronize data and view 
component.$update();  

//自動進入
<div on-click={blog.title='Hello'}>{blog.title}</div>
regular-源碼片段,基于select的r-model指令

總結:但是很顯然,臟檢查是低效的,它的效率基本上取決于你綁定的觀察者數量,在Regular中,你可以通過[@(Expression)

](https://regularjs.github.io/reference?syntax-zh#bind-once)元素來控制你的觀察者數量。

3.觀察者機制

使用ES7中的 Object.observe 方法對對象(或者其屬性)進行監控觀察,一旦其發生變化時,將會執行相應的handler。這是目前監控屬性數據變更最完美的一種方法,語言(瀏覽器)原生支持,沒有什么比這個更好了。唯一的遺憾就是目前支持廣度還不行,有待全面推廣。

4.封裝屬性訪問器(存取器get,set)

vue.js和avalon.js實現數據雙向綁定的原理就是屬性訪問器。
它使用了ES5中的定義標準屬性的Object.defineProperty 方法。

Object.defineProperty(obj, prop, descriptor)
//obj 待修改的對象
//prop 待修改的屬性名稱
//descriptor 待修改屬性的相關描述,要求傳入一個對象。
//@{param} descriptor
1.configurable ,屬性是否可配置??膳渲玫暮x包括:是否可以刪除屬性( delete ),是否可以修改屬性的 writable 、     enumerable 、 configurable 屬性。
2.enumerable ,屬性是否可枚舉。可枚舉的含義包括:是否可以通過 for...in 遍歷到,是否可以通過 Object.keys() 方法獲取屬性名稱。
3.writable ,屬性是否可重寫??芍貙懙暮x包括:是否可以對屬性進行重新賦值。
4.value ,屬性的默認值。
5.set ,屬性的重寫器。一旦屬性被重新賦值,此方法被自動調用。
6.get ,屬性的讀取器。一旦屬性被訪問讀取,此方法被自動調用。

Object.defineProperty使用示例:

var o = {};
Object.defineProperty(o, 'name', { value: 'erik'});
console.log(Object.getOwnPropertyDescriptor(o, 'name'));
   // {
        value: "erik", 
        writable: false, 
        enumerable:false,
        configurable: false
     }

Object.defineProperty(o, 'age', {
    value: 23,
    configurable: true,
    writable: true
});
console.log(o.age); // 23
o.age = 18;
console.log(o.age); // 18. 因為age屬性是可重寫的
console.log(Object.keys(o)); // []. name和age屬性都不是可枚舉的
Object.defineProperty(o, 'sex', {
    value: '女',
    writable: false
});
o.sex = '男'; // 這里的賦值其實是不起作用的
console.log(o.sex); // '女';
delete o.sex; // false, 屬性刪除的動作也是無效的

注意:

  • Object.defineProperty() 方法設置屬性時,屬性不能同時聲明訪問器屬性( set 和 get )和 writable 或者 value 屬性。 意思就是,某個屬性設置了 writable 或者 value 屬性,那么這個屬性就不能聲明 get 和 set 了,反之亦然。
  • 因為 Object.defineProperty() 在聲明一個屬性時,不允許同一個屬性出現兩種以上存取訪問控制。
4.1基于vue使用封裝屬性訪問器

首先,vuejs在實例化的過程中,會對遍歷傳給實例化對象選項中的data 選項,遍歷其所有屬性并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter。

同時每一個實例對象都有一個watcher實例對象,他會在模板編譯的過程中,用getter去訪問data的屬性,watcher此時就會把用到的data屬性記為依賴,這樣就建立了視圖與數據之間的聯系。當之后我們渲染視圖的數據依賴發生改變(即數據的setter被調用)的時候,watcher會對比前后兩個的數值是否發生變化,然后確定是否通知視圖進行重新渲染。這樣就實現了所謂的雙向數據綁定。

defineProperty
效果
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容