问题背景
- 当利用索引直接设置一个项时。例如:vm.items[indexOfItem] = newValue
- 当修改数组的长度时,例如:vm.items.length = newLength
以上两种情况并不能检测到变动
对于第一种情况可以使用:Vue.set(example1.items, indexOfItem, newValue);对于第二种情况,可以使用vm.items.splice(newLength)
为什么直接修改不能检测到变化,但是使用splice方法可以,为什么添加的对象可以变为响应式的
分析
- 在通过observe方法去观察对象的时候会实例化observer,在它的构造函数中是专门对数组做了处理,它的定义在src/core.observer/index.js中。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
这里我们只需要关注value是Array的情况,首先获取augment,这里的hasProto实际上就是判断对象中是否存在__proto__,如果存在则augment指向protoAugment,否则指向copyAugment,来看一下这两个函数的定义
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
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])
}
}
protoAugment方法是直接把target.__proto__原型直接修改为src,而copyAgument方法是遍历keys,通过def,也就是object.defineProperty去定义它自身的属性值,对于大部分现代浏览器都会走到protoAugment,那么它实际上就把value的原型指向了arrayMethods,arrMethods的定义在src/core.observer/array.js中
/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/
import { def } from '../util/index'
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]
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
})
})
可以看到,arrayMethods首先继承了Array,然后对数组中所有能改变数组自身的方法,如push,pop等这些方法的重写,重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的3个方法push,unshift,splice方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用ob.dep.notify()手动触发依赖同志,这就很好的解释了之前实例中调用vm.items.splice(newLength)方法可以检测到变化
总结
对于没有增加值的数组,手动触发了订阅者,从而进行了页面更改;对于增加了值的数组,还增加了一步,将新增的值变为响应式的。