玩转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

相关文章
相关标签/搜索