vue通过原型拦截的方式重写了数组的7个方法,首先获取到这个数组的Observer。如果有新的值,就调用observeArray对新的值进行监听,然后调用notify,通知render watcher,执行update

核心:arrayMethods 首先继承了 Array,然后对数组中所有能改变数组自身的方法,如 push、pop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 push、unshift、splice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify() 手动触发依赖通知,这就很好地解释了之前的示例中调用 vm.items.splice(newLength) 方法可以检测到变化。

// src/core/observer/array.js

// 获取数组的原型Array.prototype,上面有我们常用的数组方法
const arrayProto = Array.prototype
// 创建一个空对象arrayMethods,并将arrayMethods的原型指向Array.prototype
export const arrayMethods = Object.create(arrayProto)

// 列出需要重写的数组方法名
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
// 遍历上述数组方法名,依次将上述重写后的数组方法添加到arrayMethods对象上
methodsToPatch.forEach(function (method) {
  // 保存一份当前的方法名对应的数组原始方法
  const original = arrayProto[method]
  // 将重写后的方法定义到arrayMethods对象上,function mutator() {}就是重写后的方法
  def(arrayMethods, method, function mutator (...args) {
    // 调用数组原始方法,并传入参数args,并将执行结果赋给result
    const result = original.apply(this, args)
    // 当数组调用重写后的方法时,this指向该数组,当该数组为响应式时,就可以获取到其__ob__属性
    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)
    // 将当前数组的变更通知给其订阅者
    ob.dep.notify()
    // 最后返回执行结果result
    return result
  })
})

从上面可以看出array.js中重写了数组的push、pop、shift、unshift、splice、sort、reverse七种方法,重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中。

重写完数组的上述7种方法外,我们还需要将这些重写的方法应用到数组上,因此在Observer构造函数中,可以看到在监听数据时会判断数据类型是否为数组。当为数组时,如果浏览器支持__proto__,则直接将当前数据的原型__proto__指向重写后的数组方法对象arrayMethods,如果浏览器不支持__proto__,则直接将arrayMethods上重写的方法直接定义到当前数据对象上;当数据类型为非数组时,继续递归执行数据的监听。

参考:
vue中是如何监听数组变化?
响应式数据(数组)检测变化的注意事项

Logo

前往低代码交流专区

更多推荐