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 方法中,我們呼叫了指定的回撥,並將新值作為引數進行傳遞。