1. 程式人生 > >玩轉Vuejs--數組監聽

玩轉Vuejs--數組監聽

reverse urn ted pat http 開始 style gin mutating

Vue中對數據的監聽是依靠Object.defineProperty來實現的,defineProperty只能對Object類型的對象進行操作,並且是對Object[key]的操作進行定義,第一數組中並沒有屬性、key的這種結構,第二數組中會有基本類型的值存在,那麽數組監聽是怎麽做的呢? 一、首先考慮下數組變化的情況,主要有以下幾種:
  1. 數組本身的賦值;
  2. 數組push等方法的使用導致的變化;
  3. 數組中的值變化導致的變化;
  4. 操縱數組長度導致的數組變化;
二、接下來對上面變化的情況依次分析以下: 1.數組本身賦值的情況,這種情況顯然跟對象的監聽是一致的,直接使用defineProperty對數據進行監聽就可以了,寫個簡單的例子看下:
<!
DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="./../../dist/vue.js"></script> </head> <body> <div></div> <div id="demo"> <div> {{testArry}} </div> <
input type="button" value="按鈕" @click=‘clickHandler‘/> </div> </body> <script> new Vue({ el:"#demo", data: { testArry: [1, 2, 3] }, methods:{ clickHandler(){ this.testArry = [4, 5, 6]//直接賦值
//this.testArry[0] = 5; //this.testArry = this.testArry;//改變數組中的值
//this.testArry.push(6);//調用方法

     //this.testArry.length = 1;//改變長度
} } }); </script> </html>

testArry是data(key、value形式)的一個屬性,在初始化的時候 new Observer的時候會調用defineReactive進行正常監聽,數據更新時通知訂閱的watchers進行更新;

技術分享圖片

2.數組push等操作改變數據時想要監聽到數據的變化是沒辦法繼續通過defineProperty來實現的,需要直接監聽push等方法,在調用方法時進行監聽,所以考慮對數組原型上的方法進行hook,之後再將hook後的方法掛在到所要監聽的數組數據的 __proto__上即可,過程如下:

首先hook數組原型方法(如push)

 var arrayProto = Array.prototype;
  var arrayMethods = Object.create(arrayProto);

  var methodsToPatch = [
    ‘push‘,
    ‘pop‘,
    ‘shift‘,
    ‘unshift‘,
    ‘splice‘,
    ‘sort‘,
    ‘reverse‘
  ];

  /**
   * Intercept mutating methods and emit events
   */
  methodsToPatch.forEach(function (method) {
    // cache original method
    var original = arrayProto[method];
    def(arrayMethods, method, function mutator () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];

      var result = original.apply(this, args);
      var ob = this.__ob__;
      var inserted;
      switch (method) {
        case ‘push‘:
        case ‘unshift‘:
          inserted = args;
          break
        case ‘splice‘:
          inserted = args.slice(2);
          break
      }
      if (inserted) { ob.observeArray(inserted); }
      // notify change
      ob.dep.notify();//通知watchers
      return result
    });
  });

這裏沒有hook沒有直接在Array.prototype上做,而是重新創建了一份原型對象 arrymethods 出來,既保留了方法的整個原型鏈,又避免了汙染全局數組原型。

之後在觀察數據時進行掛載:瀏覽器支持 __proto__ 那麽直接掛載到數組的 __proto__上;不支持重新定義一份到數組本身;

var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, ‘__ob__‘, this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods); //支持__proto__:此處直接進行掛載
      } else {
        copyAugment(value, arrayMethods, arrayKeys); //不支持__proto__:直接定義到數組上
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };

  /**
   * Augment a target Object or Array by intercepting
   * the prototype chain using __proto__
   */
  function protoAugment (target, src) {
    /* eslint-disable no-proto */
    target.__proto__ = src;
    /* eslint-enable no-proto */
  }

  /**
   * Augment a target Object or Array by defining
   * hidden properties.
   */
  /* istanbul ignore next */
  function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      def(target, key, src[key]);
    }
  }

這裏需要註意數據更新方面,Vue的數據更新都是通過依賴收集器(Dep實例)通知觀察者(Watcher實例)來進行的。數組這裏與對象的初始化不同,從性能上考慮掛載的方法在最開始就會且僅會初始化一次,那麽就會導致dep實例的初始化與更新不在同一個作用域下。Vue的處理是給數組一個 __ob__的屬性用來掛載數據,在push等操作觸發hook 時再從數據的__ob__屬性上取出 dep進行通知,這裏處理的就很靈性了。

3.數組中的值變化,如果是對象或數組顯然會遞歸處理直到基本類型,Vue對基本類型的數據是不進行觀察的,主要也無法建立起監聽,所以數組下標直接改變數組值這種操作不會觸發更新;

4.數組長度的變化,無法監聽,所以數組長度變化也不會觸發更新;

三、總結:

Vue對數據的監聽有兩種,一種是數組本身的變化,直接通過Object.defineProperty實現;另一種是通過方法操縱數組,此時會hook原型上的方法建立監聽機制;對於數組下標以及長度的變化沒有辦法直接建立監聽,此時可以通過$set進行更新(會調用hook中的splice方法觸發更新);對於數組長度變化導致的數據變化無法監聽,如果想觸發只能通過hack方式調用hook中的方法進行更新;

玩轉Vuejs--數組監聽