接触过Vue的人基本都知道,Vue的数据绑定是通过ES5的getter和setter实现的,查看Vue源码,目录结构如下
这里写图片描述
observer目录下面的模块实现Vue的数据绑定功能,从文件夹和文件的命名来看可以知道采用的是观察者设计模式,看来有必要对常见的几种设计模式补补课了。

  这里我不想详细分析整个数据绑定的实现过程,网上分析的很好的文章有很多,我只想说说我在看完这部分代码后的想法。Vue数据绑定其实就是通过对数据进行递归操作最后就是对两种类型的数据进行getter和setter,一种就是除Object外的基本数据类型,另外一种就是数组。对于第一类是通过ES5的 Object.defineProperty添加get和set属性。第二类就是通过自定义方法dependArray 专门对数组进行操作。打开看dependArray 的源码:

/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let 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七种方法进行了重写,这里需要注意的是作者并没有重写Array累的原型方法,而是通过改变Array实例的__proto__属性,这样避免了对Array原始属性的破坏而造成的全局污染。但是如果我们操作数组的时候不用这几种方法,例如:

this.person.favorite[0] = "apple";

这样视图是不会发生出发视图改变的。居然这样可不可给素组对象里面的属性加上set和get属性呢?首先看看数组的结构:

这里写图片描述
看着是不是有点眼熟,他里面也是以键值对的形式存在的。那么以为着我们是不是也可以像普通对象那样去处理呢?身为程序猿,要有no zuo no die 的精神,试试为数组加上get和set属性:

function Observer(value) {
    this.value = value;
    this.walk(value);
  }
  Observer.prototype.walk = function(obj) {
    var keys = Object.keys(obj);
    for(var i = 0; i < keys.length; i++) {
      // 给所有属性添加 getter、setter
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  };
  
  var dep = [];
  
  function defineReactive(obj, key, val) {
    // 有自定义的 property,则用自定义的 property
    var property = Object.getOwnPropertyDescriptor(obj, key);
    if(property && property.configurable === false) {
      return;
    }
    
    var getter = property && property.get;
    var setter = property && property.set;
  
    // 递归的方式实现给属性的属性添加 getter、setter
    var childOb = observe(val);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function() {
        var value = getter ? getter.call(obj) : val;
        dep.push(value);
        return value;
      },
      set: function(newVal) {
        var value = getter ? getter.call(obj) : val;
        // set 值与原值相同,则不更新
        if(newVal === value) {
          return;
        }
        if(setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
       
        // 给新赋值的属性值的属性添加 getter、setter
        childOb = observe(newVal);
        console.log("数组改变了位置["+key+"]的值改变了");
      }
    });
  }
  
  function observe(value) {
    if(!value || typeof value !== 'object') {
      return;
    }
    return new Observer(value);
  }

运行结果:
这里写图片描述

看来可以啊!但是作者为什么不这样干呢?

下面我们看看如果我们改变一下person.fav的值:
这里写图片描述

  这样之后我们会发现当我们再次修改person.fav[0]位置上的值的时候,发现我们之前设置的setter属性不起作用了。这个时候因为对象的因为在赋值为[]的时候后数组的已经被删除掉了,所以这个时候就不生效了,这是需要对新生成的数组重新进行definedProperty操作。但是当你重新定义后,你还是会遇见各种set无法生效的问题,比如我通过person.fav[100]=“xx”,这样数组的长度就会达到100,或者直接通过person.fav.length=xx,这样去增加或者减小数组的长度…当然我们可以打各种补丁去重新定义,但是这样一样每次数组变化我们都需要进行重新定义。这样会严重影响前端的性能。

总结:

   YY始终还是只能停留在YY上,这样是没有必要的,因js的数组操作形式太多了,而且经常会对数组中的元素进行增加删除操作,这样会导致之前定义的key值无效,相反普通的json对象就不会经常增删属性,只是修改属性的值。如果要对数组的key进行get和set操作,会严重影响前端的性能。

Logo

前往低代码交流专区

更多推荐