数组没有使用Object.defineProperty() 定义数组每项的setter和getter方法。
(1)问题:调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,这就会造成使用上述方法改变数组后,页面上并不能及时体现这些变化,也就是数组数据变化不是响应式的。
(2)解决:vue重写了数组的push、splice、pop等方法。
1、重写数组的push、pop、shift、unshift、splice、sort、reverse七种方法。重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中。
2、对数组中的每一项进行深度观察(需是对象类型)
(3)原理图:
(4)注意:
1、当为数组时,如果浏览器支持__proto__,则直接将当前数据的原型__proto__指向重写后的数组方法对象arrayMethods,
2、当为数组时,如果浏览器不支持__proto__,则直接将arrayMethods上重写的方法直接定义到当前数据对象上;
3、当数据类型为非数组时,继续递归执行数据的监听。
(5)vue重写数组的源码:
// 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
})
})
this.observeArray(value) // 对数组中的每一项进行深度监控(需要是对象类型)