vue 双向数据绑定(补充)--数组

上回写到,vue里通过new Observer实例,来对数据进行劫持,重写get和set,在set里会触发订阅者的更新,从而实现数据同步更新。
补充一下数组的处理

var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep(); //1 订阅者容器, 用于存放所有订阅者
    this.vmCount = 0;
    def(value, '__ob__', this); //通过Object.defineProperty给value定义一个__ob__属性
    if (Array.isArray(value)) { //判断是否是数组
      if (hasProto) { // var hasProto = '__proto__' in {};
        protoAugment(value, arrayMethods); //处理原型链, 
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value); //内部对数组的每一项,又调用了observe(items[i]);
    } else { 
      this.walk(value);
    }
  };

protoAugment

function protoAugment (target, src) {
    /* eslint-disable no-proto */
    target.__proto__ = src; //target的原型指向src
    /* eslint-enable no-proto */
  }

上面调用中,target传入的就是当前数组,src传入了arrayMethods

  • arrayMethods: Object.create(Array.prototype); 继承自Array.prototype的对象
//紧接着给 arrayMethods 重新定义了以下方法
var methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
  ];
 //定义处理方法并发出对应的事件
methodsToPatch.forEach(function (method) {
    // cache original method
    //获取原型上原本的方法 arrayProto = Array.prototype
    var original = arrayProto[method];
    //def方法就是调用Object.defineProperty()来给对象设置属性
    def(arrayMethods, method, function mutator () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ]; //获取参数,例如:splice(start, length, item1, item2)
		//执行原型链中默认的处理方法,
      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(); //触发订阅者更新操作
      return result
    });
  });

综上,只有数组的push, pop , shift, unshift, splice, sort, reverse操作才能出发双向更新

copyAugment

//当不支持__proto__时
function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      def(target, key, src[key]); //给数组定义方法,用默认的
    }
  }
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

如果想要修改数组的某一项,需要调用vue的set方法,

function set (target, key, val) {
    if (isUndef(target) || isPrimitive(target) 
    ) {
      warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
    }
    //判断target是否数组, 并且key是有效的索引值
    if (Array.isArray(target) && isValidArrayIndex(key)) {
      target.length = Math.max(target.length, key);
      target.splice(key, 1, val); //调用splice方法,splice会触发dep.notify(),从而出发订阅者更新操作
      return val
    }
    //已有的对象属性,且不再原型上
    if (key in target && !(key in Object.prototype)) {
      target[key] = val; //直接赋值,会触发set方法
      return val
    }
    var ob = (target).__ob__;
    if (target._isVue || (ob && ob.vmCount)) {
      warn( //不能给vue实例添加属性
        'Avoid adding reactive properties to a Vue instance or its root $data ' +
        'at runtime - declare it upfront in the data option.'
      );
      return val
    }
    if (!ob) { //如果target是新增的数据(说明不需要数据拦截), 直接赋值即可
      target[key] = val;
      return val
    }
    defineReactive$$1(ob.value, key, val); //对新值进行拦截处理
    ob.dep.notify(); //触发更新
    return val
  }
  
  //在stateMixin()中有如下赋值
	//Vue.prototype.$set = set;
发布了66 篇原创文章 · 获赞 13 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/haoyanyu_/article/details/100031327