Array变化侦测

1.为什么Array的侦测要和Object的侦测区分实现呢?

  • Object是通过getter、setter实现的侦测,但数组中使用push等方法改变数组时,不会触发getter、setter
  • 在数组中的元素如果是个对象,也要对他们的子属性进行监听。
  • 在数组新增一个对象元素时,也要对新增的元素进行监听。

虽然Array的原型最终还是Object,但是正是由于Array的这些特性,导致了对Object的那一套侦测方式无法直接使用在数组Array中。

2.Array对象中包含了什么?

想要对Array进行监听,就需要了解Array里面到底有什么。

我们直接打印数组对象的原型

console.log(Array.prototype)

可以看到Array中的一些属性

请添加图片描述

可以看到其中包含了push concat等方法。

3.监听Array对象的大致思路

从上可以得出,可以通过覆盖Array对象中的原生方法push等,实现一些我们想要的操作。

或者说是直接定义一个拦截器,覆盖数组原型 Array.prototype
请添加图片描述

4.拦截器的实现思路

我们需要实现的拦截器,就相当于是把数组的原型对象Array.prototype复制出来, 然后将副本原型进行修改,并让数组的实例对象的原型指向该副本原型。

整理后发现,Array中有7个可以改变自身的方法:push、pop、shift、unshift、splice、sort、reverse。

所以只需要先重写这7个方法即可。

我们可以定义如下的拦截方式:

// 得到原生数组原型
const arrayProto = Array.prototype
// 相当于复制数组原型对象。
const arrayMethods = Object.create(arrayProto);

['push','pop','shift','unshift','splice','sort','reverse'].forEach(function (method) {
  // 取出原生数组原型中的方法
  const original = arrayProto[method]
  // 监听副本中对应的方法
  Object.defineProperty(arrayMethods, method, {
    value: function mutator (...args) {
      // 拦截!处理完自己的逻辑后,调用原生的方法
      return original.apply(this, args)
    },
    enumerable: false,
    writable: true,
    configurable: true
  })
})

5.拦截器怎么覆盖Array的原型

我们要的效果:

  • 拦截器不能直接覆盖Array原型,会污染全局的Array
  • 只针对那些被监听的数组进行拦截。

大致思路

Object 的变化侦测 中,我们实现了一个属性遍历侦测器Observer。是用来递归监听某个对象中所有属性的的一个类。我们就可在它递归监听时,判断该属性是否是数组,如果是数组,则在该子对象的原型中添加我们的副本原型(也就是我们的监听器)。

class Observer {
  constructor(value) {
    this.value = value
    
    if (Array.isArray(value)) {
      // 修改数组原型
      value.__proto__ = arrayMethods
    } else {
        this.walk(value);
    }
  }
  
  walk(obj) {
    for (let key in obj) {
      defineReactive(obj, key, obj[key]);
      console.log('value', obj[key]);
      console.log('key', key);
    }
  }
}

**注意:**如果浏览器不支持_proto_,就手动遍历副本原型的属性

// 判断浏览器是否能使用__proto__
const hasProto = '__proto__' in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

class Observer {
  constructor(value) {
    this.value = value
    
    if (Array.isArray(value)) {
      // 根据是否支持proto来决定使用哪种方式覆盖原型对象。
      const augment = hasProto ? protoAugment : copyAugment
    } else {
        this.walk(value);
    }
  }
  
  walk(obj) {
    for (let key in obj) {
      defineReactive(obj, key, obj[key]);
      console.log('value', obj[key]);
      console.log('key', key);
    }
  }
  
  // 三个参数:target:要修改的对象;src:用于替换的对象,keys:替换对象的字段名
  function protoAument (target, src, keys) {
      target.__proto__ = src
  }
  
  function copyAugment(target, src, keys) {
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      // 修改原型对象
      def(target, key, src[key])
    }
  }
}

 // 修改原型对象的工具方法
def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

注意:这里遍历对象属性为什么用 Object.getOwnPropertyNames 而不是Object.keys或者for in呢:

因为Array.prototype 中的属性都是不可枚举的属性。使用 Object.keys 和 for in 无法遍历到它们

请添加图片描述

6.收集依赖

在Observer类中存储依赖。

收集依赖时。每个值上新增一个_ob_字段,用于存储该值的Observer实例。判断某个元素已经创建Observer监听实例,则不用再次创建。

7.Observer的简单逻辑流程

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值