在通过 observe 方法去观察对象的时候会实例化 Observer,在它的构造函数中是专门对数组做了处理
覆盖原型或原型上某些方法
src/core/observer/index.ts
export const hasProto = '__proto__' in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
// Object.getOwnPropertyNames 方法返回一个由指定对象的所有自身属性的属性名组成的数组
export class Observer {
dep: Dep
vmCount: number
constructor(public value: any, public shallow = false, public mock = false) {
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (isArray(value)) {
if (!mock) {
if (hasProto) {
(value as any).__proto__ = arrayMethods
} 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 {
// ...
}
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
// 遍历数组,为数组的每一项设置观察,处理数组元素为对象的情况
observe(value[i], false, this.mock)
}
}
}
从上面代码可以看出,如果value是数组:
1.如果是浏览器支持_proto_属性,那就用arrayMethod覆盖Array实例value的原型,而不是覆盖所有数组的原型;
2.如果不支持_proto_实例,那么就遍历数组,def(value, key, arrayMethods[key]),那就对数组的某些方法做覆盖;
3.一般默认为是深度响应式,调用this.observeArray(value),对数组里面的值做响应式处理
下面看一下arrayMethods这个对象里面做了什么,:
改写Array原型的7个方法
在改写的方法中手动触发依赖更新
src/core/observer/array.ts
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// cache original 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
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
if (__DEV__) {
} else {
ob.dep.notify()
}
return result
})
})
export function def(obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
-
克隆一份Array原型,
const arrayProto = Array.prototype,防止改掉所有数组,创建一个以Array.prototype为原型的对象arrayMethods,const arrayMethods = Object.create(arrayProto); -
需要覆盖7个方法,
push,pop,shift,unshift,splice,sort,reverse,这7个方法改变了原数组; -
遍历这7个方法,
def(arrayMethods, method, function mutator(...args) {}),将每个方法改写值定义到arrayMethods对象上,用户使用push方法,实际上使用的是arrayMethods.push方法,而arrayMethods.push实际上是函数mutator,接下来看一下mutator函数的逻辑: -
首先每次从原型中拿出原始的方法
original = arrayProto[method],执行原始的方法,保持数组原始的行为,const result = original.apply(this, args),push,unshift,splice都是可能会向数组插入值的,用inserted变量来标识插入的值,调用ob.observeArray(inserted),看看插入的是不是对象,如果是对象,就将其转变为响应式; -
最后调用
ob.dep.notify()手动通知更新依赖,只要用户调用这7个方法,vue内部才会走到这一步,触发依赖更新
那么数组的依赖是怎么收集的呢?往下看:
数组收集依赖
src/core/observer/index.ts
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
let childOb = !shallow && observe(val, false, mock)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
if (__DEV__) {
} else {
dep.depend()
}
if (childOb) {
childOb.dep.depend()
if (isArray(value)) {
dependArray(value)
}
}
}
return isRef(value) && !shallow ? value.value : value
},
//...
})
}
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
if (e && e.__ob__) {
e.__ob__.dep.depend()
}
if (isArray(e)) {
dependArray(e)
}
}
}
// 举个数组例子
{
list1:[1,2,3,4,5],
list2:[{name:"Cristiano"}]
}
-
当读取
list1或者list2这个key的时候,就会触发它的getter,收集对应的依赖 -
调用
dependArray(value),遍历数组,取出数组的每一项,如果是对象,被监测过,有_ob_属性,就收集这个对象的依赖,通过上面的observeArray方法知道,数组里面的对象会被监测,转换为响应式,递归执行
数组的vm.$set方法
export function set(
target: any[] | Record<string, any>,
key: any,
val: any
): any {
const ob = (target as any).__ob__
//如果是数组并且key是个有效的索引值
if (isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
//splice 数组拦截器会监听到变化
target.splice(key, 1, val)
// when mocking for SSR, array methods are not hijacked
if (ob && !ob.shallow && ob.mock) {
observe(val, false, true)
}
return val
}
}
可以看到,对于数组,对传递的索引key做了处理,set内部也是调用改写的7个方法中的splice方法,实际上使用的是arrayMethods.splice方法,会调用ob.dep.notify触发依赖更新,
如果数组是响应式的,那么肯定有_ob_属性,如果往数组中添加对象,在 observe函数中,这个对象也会被转换为响应式
数组的vn.$delete
export function del(target: any[] | object, key: any) {
if (isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
}
删除方法直接调用splice删除,实际上使用的是arrayMethods.splice方法,会调用ob.dep.notify触发依赖更新
所以我们平时要改变数组,可以直接用splice方法就好了