一、数组监听与对象监听的不同

对象的监听是直接递归使用Object.defineProperty重新定义数组的每个属性,而数据是改写数组的7个数组方法:push,pop,shift,unshift,sort,splice,reverse

二、实现原理

vue在数据初始化时调用initData方法,然后通过new Observer对数据进行监测,然后对数据进行判断,如果是数组并且支持原型链就会执行protoAugment让目标原型链指向arrayMethods,arrayMethods用来改写数组的原型方法。内部会采用函数劫持的方式,当用户调用这些方法(push,pop,shift,unshift,sort,splice,reverse)之后,还会调用原数组的方法进行更新数组。拿到原数组的方法,然后重新定义这些方法。

        用户调方法时走的就是这个重写的mutator函数,这个函数还是会调用数组原有的方法,重写的mutator函数中会调用原生的方法,对新增数组的方法push,unshift,splice可以帮我们更新数组中的新增一项,对插入的数据使用observeArray再次进行监测,最后通过dep.notify通知视图更新。

源码:

1.在observer方法中


if (Array.isArray(value)) { // 如果是数组
  if (hasProto) { // 判断是否支持原型链
     protoAugment(value, arrayMethods) // arrayMethods就是改写数组的原型方法
  } else {
     copyAugment(value, arrayMethods, arrayKeys) // 如果没有原型链会走def方法添加__ob__属性
  }
  this.observeArray(value) // 深度监测数组中的每一个元素,遍历一遍数组,调用observe方法进行监测
} else {
  this.walk(value)
}

2.protoAugment

function protoAugment (target, src: Object) {
  target.__proto__ = src // 让目标的原型链指向src
}

3.arrayMethods

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
 
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
 
// 内部会采用函数劫持的方式,当用户调用这些方法之后,还会调用原数组的方法进行更新数组
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method] // 将原方法拿到
  def(arrayMethods, method, function mutator (...args) { // 然后重新定义这些方法,用户调方法时走的就是这个mutator函数,这个函数还是会调用数组原有的方法
    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 // 新增数组的方法push,unshift,splice可以帮我们更新数组中的新增一项
    }
    if (inserted) ob.observeArray(inserted) // 对插入的数据再次进行监测,新增的数据也可能是对象类型
    // observeArray是将数组遍历一遍
    ob.dep.notify() // 通知视图更新
    return result
  })
})

4.observeArray

observeArray (items: Array<any>) { // 遍历数组,进行监测
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i]) // 必须是对象类型才能被监测,如果不是对象在oberve中会被return
                        // 如果数据已经是观察过的就不会在进行观测,会直接返回,避免进行重复监听
    }
  }

5.def方法其实就是Object.defineProperty

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
Logo

前往低代码交流专区

更多推荐