1. 程式人生 > >JavaScript中MVVM框架是如何實現雙向繫結的

JavaScript中MVVM框架是如何實現雙向繫結的

我們先來看一個簡單的實現思路。

// 定義一個變化通知的回撥
var callback = function(newVal, oldVal) {
  alert(newVal + '---' + oldVal)
}
// 定義一個普通物件作為資料模型
var data = {
  a: 10,
  level1: {
    b: 'str',
    c: [1,2,3],
    level2: {
      d: 30
    }
  }
}
// 例項化一個檢測物件,去檢測資料,並在資料發生改變的時候做出反應
var j = new Jsonob(data, callback)

上面程式碼中,我們定義了一個 callback 回撥函式,以及一個儲存著普通 json 物件的變數 data,最後例項化了一個監測物件,對 data 進行變化監測,當變化發生的時候,執行給定的回撥進行必要的變化通知,這樣,我們通過一些手段就可以達到資料繫結的效果。

Object.defineProperty

ES5 描述了屬性的特徵,提出物件的每個屬性都有特定的描述符,你也可以理解為那是屬性的屬性。。。

ES5 把屬性分成兩種,一種是資料屬性,一種是訪問器屬性,我們可以使用 Object.defineProperty() 去定義一個數據屬性或訪問器屬性。如下程式碼:

var obj = {}
obj.name = 'yyf'

上面的程式碼我們定義了一個物件,並給這個物件添加了一個屬性 name,值為 ‘yyf’,我們也可以使用 Object.defineProperty() 來給物件定義屬性,上面的程式碼等價於:

var obj = {}
Object.defineProperty(obj, 'name'
, { value: 'yyf', //屬性的值 writable: true, //是否可寫 enumerable: true, //是否能夠通過for in列舉 configurable: true //是否可使用delete刪除 })

這樣我們就使用 Object.defineProperty 給物件定義了一個屬性,這樣的屬性就是資料屬性,我們也可以定義訪問器屬性:

var obj = {}
Object.defineProperty(obj, 'age', {
  get: function() {
    return 20
  },
  set: function(newVal)
{
this.age += 20 } })

訪問器屬性允許你定義一對 getter/setter ,當你讀取屬性值的時候底層會呼叫 get 方法,當你去設定屬性值的時候,底層會呼叫 set 方法。

知道了這個就好辦了,我們再回到最初的問題上面,如何檢測一個普通物件的變化,我們可以這樣做:

遍歷物件的屬性,把物件的屬性都使用 Object.defineProperty 轉為 getter/setter ,這樣,當我們修改一些值的時候,就會呼叫 set 方法,然後我們在 set 方法裡面,回撥通知,不就可以了嗎,來看下面的額程式碼:

// index.js
const OP = Object.prototype
export class Jsonob {
  constructor(obj, callback) {
    if(OP.toString.call(obj) !== '[object Object]') {
      console.error('This parameter must be an object:' + obj)
    }
    this.$callback = callback
    this.observe(obj)
  }
  observe(obj) {
    Object,keys(obj).forEach(function(key,index,keyArray) {
      var val = obj[key]
      Object.defineProperty(obj, key, {
        get: function(){return val},
        set: (function(newVal){
          this.$callback(newVal)
        }).bind(this)
      })
      if(OP.toString.call(obj[key]) === '[object Object]') {
        this.observe(obj[key])
      }
    }, this)
  }
}

上面程式碼採用 ES6 編寫,index.js 檔案中匯出了一個 Jsonob 類,constructor 建構函式中,我們保證了傳入的物件是一個 { } 或 new Object() 生成的物件,接著快取了回撥函式,最後呼叫了原型下的 observe 方法。

observe() 方法是真正實現監測屬性的方法,我們使用 Object.kes(obj).forEach 迴圈 obj 所有可列舉的屬性,使用 Object.defineProperty 將屬性轉換為訪問器屬性,然後判斷屬性的值是否是一個物件,如果是物件的話再進行遞迴呼叫,這樣一來,我們就能保證一個複雜的普通 json 物件中的屬性以及值為物件的屬性都轉換成訪問器屬性。

最後,在 Object.defineProperty 的 set 方法中,我們呼叫了指定的回撥,並將新值作為引數進行傳遞。