淺析VUE雙向繫結原理,實現資料監聽並通知訂閱者
阿新 • • 發佈:2019-02-04
淺析VUE雙向繫結原理,實現屬性變化的監聽
本文引用了“鄧木琴居然被盜用了”的文章內容,博文地址: https://segmentfault.com/a/1190000006599500
一、VUE雙向繫結原理簡單介紹
Vue的雙向繫結是通過資料劫持結合釋出-訂閱者模式實現的,即通過Object.defineProperty監聽各個屬性的setter,然後通知訂閱者屬性發生變化,觸發響應的回撥。原理圖如下:
整個過程分為以下幾步:
1、Observer通過Object.defineProperty實現對屬性的變化監聽,在變化是通知訂閱者。
2、Compile,對每個元素節點的指令進行掃描和解析,根據指令模板替換資料,以及繫結相應的更新函式
2、Watcher是訂閱者,是Observer和Compile的中間紐帶,負責將變化的資料更新到檢視,
二、屬性變化的監聽
直接上程式碼:
```
class Observer{
//構造器
constructor(data){
this.className = 'Observer';
this.observe(data);
}
//監聽資料data的所有屬性
observe(data) {
if(!this.isObj(data)){
return;
}
Object.keys(data).forEach((key)=>{
this.defineReative(data,key,data[key]);
})
}
//判斷obj是否為物件,是返回true
isObj(obj) {
return !!data&&(typeof obj ==='object')
}
//監聽data的key屬性
defineReative(data,key,val){
this.observe(val);//如果子屬性是物件的話,繼續監聽
Object.defineProperty(data,key,{
enumerable: true, // 可列舉
configurable: true, // 可配置
get: function() {
return val;
},
set: function(newVal) {
console.log('監聽資料變化 ', val, ' --> ', newVal);
val = newVal;
}
})
}
}
```
簡單解析下這段程式碼,程式碼使用了ES6的語法:
函式defineReactive()中是用Object.defineProperty對屬性進行了重新定義,從而在setter函式中監聽屬性的變化。不熟悉Object.defineProperty的同學百度一下。^.^
測試一下,
```
let data = {
name:'xxx',
age:18
}
let observe = new Observer(data);
data.name = 'lalala';
```
開啟控制檯會輸出:<br/>
監聽資料變化 xxx --> lalala。<br/>
現在已經監控到資料發生變化,即在setter中通知訂閱者屬性發生即可。所以需要實現訂閱者的管理,包括增刪改查,通知訂閱者屬性變化。
```
//管理訂閱者
class Dep{
constructor(){
//訂閱者列表
this.subs = [];
//指向當前訂閱者
this.target = null;
}
//新增訂閱者
addSub(sub){
this.subs.push(sub);
}
//通知訂閱者屬性發生變化
notify(newVal) {
this.subs.forEach((sub) => {
sub.update(newVal);
});
}
//刪改查等等操作暫時略
}
```
上面我們已經知道,可以在setter中通知訂閱者發生變化,那在什麼地方進行訂閱呢,即需要使用屬性的地方,當然就是在getter裡訂閱,所以defineReactive修改如下:
```
defineReative(data,key,val){
this.observe(val);//如果子屬性是物件的話,繼續監聽
let dep = new Dep();
Object.defineProperty(data,key,{
enumerable: true, // 可列舉
configurable: true, // 可配置
get: function() {
if(Dep.target){
dep.addSub(Dep.target);
}
return val;
},
set: function(newVal) {
if(val !== newVal){
dep.notify(newVal);
val = newVal;
//console.log('監聽資料變化 ', val, ' --> ', newVal);
}
}
})
}
```
Watcher如下:
```
class Watcher{
//監控data的key屬性,cb是發生變化時的回撥函式
constructor(data,key,cb){
this.data = data;
this.key = key;
this.cb = cb;
this.value = this.get();
}
update(newVal) {
this.run(newVal);
}
run(newVal) {
let oldVal = this.value;
if (newVal !== oldVal) {
this.cb(newVal, oldVal);
this.value = newVal;
}
}
get(){
Dep.target = this;
this.value = this.data[this.key];//觸發getter裡訂閱
Dep.target = null;
return this.value;
}
}
```
測試一下:
let data = {
name:'xxx',
age:18
}
function monitorData(obj){
new Observer(obj);
new Watcher(obj,'name',function (newVal,oldVal) {
console.log('change,訂閱者1:'+ oldVal + '-->' + newVal);
})
}
monitorData(data);
data.name = 'xbd';
輸出為:change,訂閱者1:xxx-->xbd
到此即完成了資料的監聽和通知訂閱功能。