【玩轉Vue.js】數組監聽


Vue中對數據的監聽主要是依靠Object.defineProperty來實現的,這種實現主要是針對key/value形式的對象,對數組中值的變化是無能為力的,那么該如何對數組中的數據進行監聽呢,下面分析一下Vue對數組類型數據的監聽方式。
 
一、首先考慮下數組變化的情況,主要有以下幾種:
  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中的方法進行更新,如官方推薦的splice等;

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM