1. 程式人生 > >淺析VUE雙向繫結原理,實現資料監聽並通知訂閱者

淺析VUE雙向繫結原理,實現資料監聽並通知訂閱者

淺析VUE雙向繫結原理,實現屬性變化的監聽



本文引用了“鄧木琴居然被盜用了”的文章內容,博文地址: https://segmentfault.com/a/1190000006599500

一、VUE雙向繫結原理簡單介紹

Vue的雙向繫結是通過資料劫持結合釋出-訂閱者模式實現的,即通過Object.defineProperty監聽各個屬性的setter,然後通知訂閱者屬性發生變化,觸發響應的回撥。
原理圖如下:

整個過程分為以下幾步:
1、Observer通過Object.defineProperty實現對屬性的變化監聽,在變化是通知訂閱者。
2、Compile,對每個元素節點的指令進行掃描和解析,根據指令模板替換資料,以及繫結相應的更新函式
2、Watcher是訂閱者,是Observer和Compile的中間紐帶,負責將變化的資料更新到檢視,


二、屬性變化的監聽

1、監聽屬性變化(Object.defineProperty)
直接上程式碼:
```
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

到此即完成了資料的監聽和通知訂閱功能。