Object.defineProperty

直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

const proxy = function(target,key){
           let value ;

           Object.defineProperty(target,key,{
               set(val){
                   value = val;
                   console.info('set')
               },
               get(){
                    console.info('get')
                   return value;
               }
           })
    }
    const obj = Object.create(null);

    proxy(obj,'arr');

    obj.arr = [];
    

打印出

'set'

在Vue中的应用

众所周知,Vue的数据更新触发页面渲染,采用的就是Object.defineProperty 中对对象属性set的监听。

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    var value = getter ? getter.call(obj) : val;
    /* eslint-disable no-self-compare */
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    /* eslint-enable no-self-compare */
    if (process.env.NODE_ENV !== 'production' && customSetter) {
      customSetter();
    }
    if (setter) {
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    childOb = !shallow && observe(newVal);
    dep.notify();
  }
});

在Vue的实例初始化时,将组件的初始化数据,逐级实例成可观察的对象。

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};

Object.defineProperty 存在的问题


  1. 如果添加的属性值是数组,当使用push等方法改变数组,不会触发set

    const obj = Object.create(null);
    
    proxy(obj,'arr');
    
    obj.arr = [];
    obj.arr.push(1);

    不会打印出

    'set'
  2. 如果添加的属性值是多层级的对象,对于深层级的修改,不会触发到set

    // 紧跟前面例子
    proxy(obj,'deepObj');
    
    obj.deepObj = {a:{b:c:1}}
    obj.deepObj.a.b.c = 2

    不会打印出

    'set'

Vue针对存在问题的处理

  1. 对于数组的更新问题
    Vue重写Array原型链上的几个方法,使得当使用push,pop,shift,unshift,splice,sort,reverse这几个方法时,也能触发页面刷新。

    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);[
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    .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();
        return result
      });
    });
    
  2. 针对多层级对象,如果没有在data定义,直接修改值,是不会触发页面更新的。

    var vm = new Vue({ 
        data(){
            return  { a: 1 } ;
        }
    })
    
    vm.a  = 2;  //会触发页面刷新
    vm.b = 3;   //不会触发页面刷新

    此时,如果想更改没有定义属性,需要使用官方推荐的实例$set方法。

    vm.$set(vm.data, 'b', 3)   //会触发页面刷新
    

大概了解了Vue的数据更新原理,就可以开始联想Vue3.0的Proxy啦~


southnan0
156 声望8 粉丝

欲买桂花同载酒,终不似,少年游


« 上一篇
脚手架 rename
下一篇 »
nginx 笔记