vue源码分析-响应式系统(三)

上一节,我们深入分析了以data,computed为数据创建响应式系统的过程,并对其中依赖收集和派发更新的过程进行了详细的分析。然而在使用和分析过程中依然存在或多或少的问题,这一节我们将针对这些问题展开分析,最后我们也会分析一下watch的响应式过程。这篇文章将作为响应式系统分析的完结篇。

7.12 数组检测

在之前介绍数据代理章节,我们已经详细介绍过Vue数据代理的技术是利用了Object.defineProperty,Object.defineProperty让我们可以方便的利用存取描述符中的getter/setter来进行数据的监听,在get,set钩子中分别做不同的操作,达到数据拦截的目的。然而Object.definePropertyget,set方法只能检测到对象属性的变化,对于数组的变化(例如插入删除数组元素等操作),Object.defineProperty却无法达到目的,这也是利用Object.defineProperty进行数据监控的缺陷,虽然es6中的proxy可以完美解决这一问题,但毕竟有兼容性问题,所以我们还需要研究VueObject.defineProperty的基础上如何对数组进行监听检测。

7.12.1 数组方法的重写

既然数组已经不能再通过数据的getter,setter方法去监听变化了,Vue的做法是对数组方法进行重写,在保留原数组功能的前提下,对数组进行额外的操作处理。也就是重新定义了数组方法。

var arrayProto = Array.prototype;
// 新建一个继承于Array的对象
var arrayMethods = Object.create(arrayProto);

// 数组拥有的方法
var methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

arrayMethods是基于原始Array类为原型继承的一个对象类,由于原型链的继承,arrayMethod拥有数组的所有方法,接下来对这个新的数组类的方法进行改写。

methodsToPatch.forEach(function (method) {
   
  // 缓冲原始数组的方法
  var original = arrayProto[method];
  // 利用Object.defineProperty对方法的执行进行改写
  def(arrayMethods, method, function mutator () {
   });
});

function def (obj, key, val, enumerable) {
   
    Object.defineProperty(obj, key, {
   
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
  }


这里对数组方法设置了代理,当执行arrayMethods的数组方法时,会代理执行mutator函数,这个函数的具体实现,我们放到数组的派发更新中介绍。

仅仅创建一个新的数组方法合集是不够的,我们在访问数组时,如何不调用原生的数组方法,而是将过程指向这个新的类,这是下一步的重点。

回到数据初始化过程,也就是执行initData阶段,上一篇内容花了大篇幅介绍过数据初始化会为data数据创建一个Observer类,当时我们只讲述了Observer类会为每个非数组的属性进行数据拦截,重新定义getter,setter方法,除此之外对于数组类型的数据,我们有意跳过分析了。这里,我们重点看看对于数组拦截的处理。

var Observer = function Observer (value) {
   
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  // 将__ob__属性设置成不可枚举属性。外部无法通过遍历获取。
  def(value, '__ob__', this);
  // 数组处理
  if (Array.isArray(value)) {
   
    if (hasProto) {
   
      protoAugment(value, arrayMethods);
    } else {
   
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
   
  // 对象处理
    this.walk(value);
  }
}

数组处理的分支分为两个,hasProto的判断条件,hasProto用来判断当前环境下是否支持__proto__属性。而数组的处理会根据是否支持这一属性来决定执行protoAugment, copyAugment过程,

// __proto__属性的判断
var hasProto = '__proto__' in {
   };

当支持__proto__时,执行protoAugment会将当前数组的原型指向新的数组类arrayMethods,如果不支持__proto__,则通过代理设置,在访问数组方法时代理访问新数组类中的数组方法。

//直接通过原型指向的方式

function protoAugment (target, src) {
   
  target.__proto__ = src;
}

// 通过数据代理的方式
function copyAugment (target, src, keys) {
   
  for (var i = 0, l = keys.length; i < l; i++) {
   
    var key = keys[i];
    def(target, key, src[key]);
  }
}

有了这两步的处理,接下来我们在实例内部调用push, unshift等数组的方法时,会执行arrayMethods类的方法。这也是数组进行依赖收集和派发更新的前提。

7.12.2 依赖收集

由于数据初始化阶段会利用Object.definePrototype进行数据访问的改写,数组的访问同样会被getter所拦截。由于是数组,拦截过程会做特殊处理,后面我们再看看dependArray的原理。

function defineReactive###1() {
   
  ···
  var childOb = !shallow && observe(val);

  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.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值