簡單易懂的雙向資料繫結解讀

Lemon就是我發表於2018-06-14

簡單易懂的雙向資料繫結解讀

資料更新檢視的重點是如何知道資料變了,只要知道資料變了,那麼接下去的事都好處理。如何知道資料變了,就是通過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

相關文章