vue2.0 Array变化数据双向绑定监听

347 阅读2分钟

为何和Object对象监听方式不一样?

对象使用Object.defineProperty进行数据劫持监听;
defineProperty是Object原型上的,数组不能使用该方法;

思路:数据获取时收集依赖,数据变化更新时通知更新;

1.数组收集依赖;

// arr实际还是存在于object对象中,获取arr时就会触发object到get操作,从而实现依赖收集;
data(){
  return {
    arr:[1,2,3]
  }
}

2.数组的通知依赖:重写操作数据的方法(pop/push/shift/unshift/sort/reverse/splice);

大致思路:

let arr = [1,2,3]
arr.push(4)
Array.prototype.newPush = function(val){
  console.log('arr被修改了')
  // 可以在这里做一些额外的操作;
  this.push(val)
}
arr.newPush(4)

源码的实现:

1.通知依赖;
2.数组新增元素的侦测:对`push``unshift``splice`3中方法分别处理,拿到新增的元素,再将其转化即可;
const arrayProto = Array.prototype
// 创建一个对象作为拦截器
export const arrayMethods = Object.create(arrayProto)

// 改变数组自身内容的7个方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (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   // 如果是push或unshift方法,那么传入参数就是新增的元素
        break
      case 'splice':
        inserted = args.slice(2) // 如果是splice方法,那么传入参数列表中下标为2的就是新增的元素
        break
    }
    if (inserted) ob.observeArray(inserted) // 调用observe函数将新增的元素转化成响应式
    ob.dep.notify() // 通知依赖
    return result
  })
})
  1. 不足之处:
// 以下两种方式处理数组无法监听数组变化;
let arr = [1,2,3]
arr[0] = 5;       // 通过数组下标修改数组中的数据
arr.length = 0    // 通过修改数组长度清空数组

// 解决:`Vue`增加了两个全局API:
//   1.Vue.set( target, propertyName/index, value );
//   2.Vue.delete( target, propertyName/index );