簡單易懂的雙向資料繫結解讀
資料更新檢視的重點是如何知道資料變了,只要知道資料變了,那麼接下去的事都好處理。如何知道資料變了,就是通過Object.defineProperty( )對屬性設定一個set函式,當資料改變了就會來觸發這個函式,所以我們只要將一些需要更新的方法放在這裡面就可以實現data更新view了
實現一個Observer
Observer是一個資料監聽器,其實現核心方法就是前文所說的Object.defineProperty( )。如果要對所有屬性都進行監聽的話,那麼可以通過遞迴方法遍歷所有屬性值,並對其進行Object.defineProperty( )處理。如下程式碼,實現了一個Observer。
Object有一個名為defineProperty的方法,可以設定訪問器屬性,比如:
var obj = {}
Object.defineProperty(obj, 'a', {
get: function(){
console.log('this is the getter')
return 1
},
set: function(){
console.log('change new value')
}
})
// 獲取物件的值
obj.a
// console.log(obj)
// 改變物件a的值
//obj.a = 1
複製程式碼
當我們執行obj.a的時候會觸發get函式,控制檯會列印'this is the getter',當我們為obj.a賦值的時候,obj.a=2;這是控制檯會列印"change new value"大家可以把getter和setter理解成獲取物件屬性值和給物件屬性賦值時的鉤子就可以了。
雙向資料繫結實現思路
我們已經知道實現資料的雙向繫結,首先要對資料進行劫持監聽,所以我們需要設定一個監聽器Observer,用來監聽所有屬性。如果屬性發上變化了,就需要告訴訂閱者Watcher看是否需要更新。因為訂閱者是有很多個,所以我們需要有一個訊息訂閱器Dep來專門收集這些訂閱者,然後在監聽器Observer和訂閱者Watcher之間進行統一管理的。接著,我們還需要有一個指令解析器Compile,對每個節點元素進行掃描和解析,將相關指令對應初始化成一個訂閱者Watcher,並替換模板資料或者繫結相應的函式,此時當訂閱者Watcher接收到相應屬性的變化,就會執行對應的更新函式,從而更新檢視。因此接下去我們執行以下3個步驟,實現資料的雙向繫結:
1.實現一個監聽器Observer,用來劫持並監聽所有屬性,如果有變動的,就通知訂閱者。
2.實現一個訂閱者Watcher,可以收到屬性的變化通知並執行相應的函式,從而更新檢視。
3.實現一個解析器Compile,可以掃描和解析每個節點的相關指令,並根據初始化模板資料以及初始化相應的訂閱器。
流程圖如下:
訂閱者模式:訂閱者模式也叫“訂閱-釋出者模式”,對於前端來說這種模式簡直無處不在,比如我們常用的xx.addEventListener('click',cb,false)就是一個訂閱者,它訂閱了click事件,當在頁面觸發時,瀏覽器會作為釋出者告訴你,可以執行click的回撥函式cb了。
eg: https://jsbin.com/biyulaz/edit?html,output
實現一個Watcher
比如,士兵與長官就是一個訂閱與釋出者的關係,士兵的所有行動都通過長官來發布,只有長官發號施令,士兵們才能執行對應的行動。
// obj是一個釋出者, 釋出資訊時呼叫dep的notify方法
obj = {
pub: function(dep){
dep.notify()
}
}
//Dep是連結訂閱者和釋出者的一個橋樑, 訂閱者將自己存入subs中, 釋出者通過pub方法來通知subs中的每一個訂閱者
function Dep(){
this.subs = []
}
Dep.prototype.notify = function () {
// 通知訂閱者, 並讓它們執行update方法
this.subs.forEach(item => {
item.update()
})
}
// 訂閱者
function Watcher(msg) {
this.msg = msg
}
Watcher.prototype.update = function () {
console.log('dom' + this.msg + '更新');
}
// 建立三個訂閱者
var a = new Watcher(1),
b = new Watcher(2),
c = new Watcher(3);
var dep = new Dep();
// 將三個訂閱者push進Dep.subs中
dep.subs.push(a)
dep.subs.push(b)
dep.subs.push(c)
// 釋出者釋出
obj.pub(dep)
複製程式碼
在這段程式碼中,obj是釋出者,Watcher例項是訂閱者,Dep用來儲存訂閱者,以及接受釋出者通知的一個媒介
proxy(es6)實現
var nameProxy = new Proxy({
name: 'gaodeng'
}, {
get: function (target, key, value) {
console.log('觸發getter鉤子函式, 值為: ', value)
return target[key];
},
set: function (target, key, value) {
console.log('觸發setter鉤子函式, 值為: ', value)
target[key] = value;
}
});
// 獲取
nameProxy.name
// 設定
nameProxy.name = '小明'
複製程式碼
最後一個完整的栗子: https://jsbin.com/yijorunepe/edit?html,output