源码位置
Array的变化侦探
根据上文Vue响应式原理-Object的变化侦探可知,Object的变化是靠setter来进行追踪的,也就是说在getter中收集依赖,在setter中来触发依赖通知变化,而Array中,当然也是在getter中进行依赖收集,但是触发依赖通知就与Object不一样了,Array本身具有7个方法可以对数组进行更改,所以我们可以拦截Array,在它的拦截器中来进行触发依赖通知。
Array的拦截器
地址见:- array.js
//保存Array原来的方法
const arrayProto = Array.prototype
//复制一份arrayProto
const arrayMethods = Object.create(arrayProto)
//Array的7个方法
const methodsToPatch = [
'push',
'pop',
'unshift',
'shift',
'splice',
'sort',
'reverse'
]
// Array拦截器,触发更新通知
methodsToPatch.forEach(method => {
// 保存原始方法
const origin = arrayProto[method]
//拦截器
def(arrayMethods, method, function mutator(...args){
})
})
通过以上代码,我们可知,Array触发更新应该在拦截器mutator中触发。但是现在我们还不知道依赖收集在哪里,所以现在要解决的问题是:
- 如何收集依赖
- 如何获取到依赖对象呢
对于Object收集依赖是通过在defineReactive中 new Dep(), 然后在getter中通过dep.depend()进行收集依赖。这里依赖可以直接放在defineReactive中的,是因为触发依赖也在defineReactive只中进行,但是Array中收集依赖和触发依赖的位置有明显的区别,我们只要能让defineReactive中和Array拦截器中都能访问到依赖,就能解决上面两点的问题。
所以,我们考虑将Array的依赖收集放在Observer中,并给每一个进行了Observer(响应式)的属性都添加上__ob__来存储这个Observer实例,从而对于述责Array拦截器,只需要通过this.__ob__就能够访问到Observer,从而访问到Observer中的dep,进行依赖触发通知。
所以现在对Observer进行修改(原始Observer见上文Vue响应式原理-Object的变化侦探)
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
def(value, '__ob__', this)
if(Array.isArray(value)) {
// 将arrayMethods给value的原型, 实现拦截
protoAugment(value, arrayMethods)
}else{
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj)
for(let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function defineReactive(data, key, val) {
const dep = new Dep()
// new Observer(val)
const childOb = observe(val)
if(typeof val === 'object') {
new Observer(val)
}
Object.defineProperty(data ,key ,{
enumerable: true,
configurable: true,
get: function() {
dep.depend()
return val
},
set: function(newVal) {
dep.notify()
if(newVal!==val) return newVal
},
})
}
// 伪代码
function observe(value, asRootDate){
if(!isObject(value))
return
// __ob__ 存在直接返回,不存在则新建
let ob;
if(hasOb(value) && ob instanceOf Observer)
ob = value.__ob__
else
ob = new Observer(value)
}
从以上代码可以看出,此时defineReactive中已经访问到dep依赖收集了。并且每一个进行了响应式的属性身上都有了一个__ob__ 对象,所以此时可以在Array拦截器直接访问
// Array拦截器,触发更新通知
methodsToPatch.forEach(method => {
// 保存原始方法
const origin = arrayProto[method]
//拦截器
def(arrayMethods, method, function mutator(...args){
let inserted;
//访问ob即Observer实例
const ob = this.__ob__
switch(method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
// inserted存在, 给新增的inserted添加响应式
if (inserted) ob.observeArray(inserted)
//通知变化
ob.dep.notify()
})
})
以上代码,不仅可以实现Array发生变化后通知变化,对于Array新增了的属性,还可以对新增的属性添加上响应式.