由于 defineReactive 所使用的 definePrototype 对数组无法进行有效的劫持,所以当 Vue 观测到数组后将会用另一种方式实现数据的监听。
if (Array.isArray(value)) {
// 判断当前环境是否有 __proto__,IE 11 以下就没有 __proto__
if (hasProto) {
// 正常情况下
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历并深度检测
this.observeArray(value)
}
// 覆盖原型链
function protoAugment (target, src: Object) {
target.__proto__ = src
}
// 通过代理覆盖原型上的函数
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
arrayMethods
一个进行加工过的数组原型,其中有关涉及修改数组内容的函数都已被挟持,触发后会通知依赖项更新,并如有新值加入则对新值进行深度监听。
const arrayProto = Array.prototype // 数组原型
export const arrayMethods = Object.create(arrayProto) // 数组原型浅拷贝,在此基础上修改,防止污染全局原型链
// 涉及修改数组内容的函数 key 值
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 遍历上面的 key 值
methodsToPatch.forEach(function (method) {
// 获取原先的函数
const original = arrayProto[method]
// 挟持这个会修改数组值的函数,分三步走
// 1. 执行该函数的原函数
// 2. 如果涉及添加新元素,则对新添加的元素进行深度检测
// 3. 通知当前值的观察者进行更新
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
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})