Object.defineProperty无法监听到数组的变化,所以vue通过拦截的方式来做数组的响应式
- 首先得到7个拦截的数组方法arrayMethods,除此之外arrayMethods也含有数组的其他方法。之所以是这七个,是因为这些方法都为增删改操作
const arrayProto = Array.prototype
// 创建一个新的对象来拓展数组的原型方法,在这些方法被调用时触发自定义的逻辑(响应式处理)
export const arrayMethods = Object.create(arrayProto)
// 定义要响应式拦截的数组方法,共七种
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
// 通过Object.defineProperty的数据描述符的value,进行拦截
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
// push、unshift、splice这些方法可以新增,新增的数据也要转变为响应式
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 将新增的数据变为响应式
if (inserted) ob.observeArray(inserted)
// notify change
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
// 通知所有watcher视图更新
ob.dep.notify()
}
return result
})
})
- 判断处理的数据是数组时,改变它的__proto__指向为arrayMethods。并不会影响全局Array原型上的方法
class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (isArray(value)) {
// 这里是数组的响应式处理
if (!mock) {
if (hasProto) {
/* eslint-disable no-proto */
// 改变当前数组对象的__proto__ 为拦截方法arrayMethods
// 当使用上述七种方法时拦截
;(value as any).__proto__ = arrayMethods
/* eslint-enable no-proto */
} else {
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
this.observeArray(value)
}
} else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 这里是对象的响应式处理
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
}