let data = {
a:{
b:1
}
}
经过new Observer(data)后变成了:
%%%%%%%%%%%%%%%%%%%%%%%%
let data = {
a:{
b: 1,
__ob__ : {value,dep,vmCount}
},
__ob__: {value,dep,vmCount}
}
我们一步一步来看看这个过程是怎么发生的。
step1
首先
observe(data)
function observe(value){
let ob = new Observer(value)
return ob
}
step2
实际就是
ob = new Observer(data)
// ob 是构造函数的实例
它做了两件事,
第一件:给data定义了一个__ob__
属性赋了三个属性
// new Observer(data)
class Observer{
constructor(value){
// 给value定义一个 __ob__ 属性,并且 value.__ob__ === this(this 就是ob)
def(value,'__ob__', this)
}
}
第二件:给ob赋了三个属性
ob :{
value: data,
dep,
vmCount
}
那么根绝第一步,我们可以推导出来
data:{
a:{
b
},
__ob__ : ob
}
总结: 每个对象都有一个 __ob__ 属性, 指向他自己的Observer实例。
第三件事:遍历data的所有属性,调用defineReactive
defineReactive(a)
let childOb = observe(a)
// 给a设置getter和setter
// 在getter/setter中通过闭包引用 childOb
// 我们从observe的返回值可知,observe返回的是一个Observer实例
// Observer实例有value和dep等属性, 也就是说childOb有value 和 dep等属性,并且 value的值就是a。
总之说, 对于属性a来说,a的getter/setter通过闭包引用了childOb, 而childOb === data.a.__ob__
childOb.depend()
其实就是data.a.__ob__.depend()
这告诉我们,当a收集依赖时,它不仅会把观察者塞到自己闭包引用的dep筐里面去,同时还会把这个观察者塞到 a.__ob__.dep
筐里去。
vue 这么做的目的是什么?
第一个筐 dep.depend()
很好理解,就是我们熟知的收集依赖,当属性值被修改时触发。
第二个筐 a.__ob.__depend()
收集的依赖的触发时机是在使用$set
或Vue.set()
给对象添加新属性时触发。
这是因为在没有proxy之前,vue没办法拦截到给对象添加属性的操作(vue2的源码中都是要先遍历所有属性,再给属性设置会响应式。后来添加的属性无法设置)。所以vue才提供了$set和Vue.set等方法让我们有能力给对象添加属性的同时触发依赖。
如何做到的呢?
Vue.set = function(obj,key,val){
defineReactive(obj, key,val)
obj.__ob__.dep.notify()
}
当我们使用
Vue.set(data.a, 'c',1)
上述代码能够触发依赖,是因为Vue.set函数中触发了
data.a.__ob__.dep
这个筐里的依赖。
所以,__ob__
属性以及__ob__.dep
的主要作用是为了添加、删除属性时有能力触发依赖。
或许你还是不太明白,为什么是obj.__ob__.dep.notify
而不是obj.dep.notify()
?