玩轉Vuejs--數組監聽

Vue中對數據的監聽主要是依靠Object.defineProperty來實現的,這種實現主要是針對key/value形式的對象,對數組中值的變化是無能爲力的,那麼該如何對數組中的數據進行監聽呢,下面分析一下Vue對數組類型數據的監聽方式。
 
1、首先考慮下數組變化的狀況,主要有如下幾種:
  1. 數組自己的賦值;
  2. 數組push等方法的使用致使的變化;
  3. 數組中的值變化致使的變化;
  4. 操縱數組長度致使的數組變化; 

 

2、接下來對上面變化的狀況依次進行分析:
 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進行更新;html

 

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

首先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__上;不支持從新定義一份到數組自己;app

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對基本類型的數據是不進行觀察的,主要也沒法創建起監聽,因此數組下標直接改變數組值這種操做不會觸發更新;this

4.數組長度的變化,沒法監聽,因此數組長度變化也不會觸發更新;spa

3、總結:prototype

Vue對數據的監聽有兩種,一種是數組自己的變化,直接經過Object.defineProperty實現;另外一種是經過方法操縱數組,此時會hook原型上的方法創建監聽機制;對於數組下標以及長度的變化沒有辦法直接創建監聽,此時能夠經過$set進行更新(會調用hook中的splice方法觸發更新);對於數組長度變化致使的數據變化沒法監聽,若是想觸發只能經過hack方式調用hook中的方法進行更新,如官方推薦的splice等;eslint

相關文章
相關標籤/搜索