Object.defineProperty(obj,prop,descriptor)
-
参数
obj: 目标对象
prop: 需要定义的属性或方法的名称
descriptor:目标属性所拥有的特性 -
可供定义的特性列表
value: 属性的值
writable: 如果为false,属性的值就不能被重写。
get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。
set: 一旦目标属性被赋值,就会调回此方法。
configurable: 如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable, configurable, enumerable)的行为将被无效化。
enumerable: 是否能在for...in循环中遍历出来或在Object.keys中列举出来。
源码:
function defineReactive (obj, key, val) {
// 生成一个新的Dep实例,这个实例会被闭包到getter和setter中
var dep = new Dep()
var property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get
var setter = property && property.set
// 对属性的值继续执行observe,如果属性的值是一个对象,那么则又递归进去对他的属性执行defineReactive
// 保证遍历到所有层次的属性
var childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
// 只有在有Dep.target时才说明是Vue内部依赖收集过程触发的getter
// 那么这个时候就需要执行dep.depend(),将watcher(Dep.target的实际值)添加到dep的subs数组中
// 对于其他时候,比如dom事件回调函数中访问这个变量导致触发的getter并不需要执行依赖收集,直接返回value即可
if (Dep.target) {
dep.depend()
if (childOb) {
//如果value是对象,那就让生成的Observer实例当中的dep也收集依赖
childOb.dep.depend()
}
if (isArray(value)) {
for (var e, i = 0, l = value.length; i < l; i++) {
e = value[i]
//如果数组元素也是对象,那么他们observe过程也生成了ob实例,那么就让ob的dep也收集依赖
e && e.__ob__ && e.__ob__.dep.depend()
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// observe这个新set的值
childOb = observe(newVal)
// 通知订阅我这个dep的watcher们:我更新了
dep.notify()
}
})
}
Dep类的定义极其简单,一个id,一个数组,他就是一个很基本的发布者-观察者模式的实现,作为一个发布者,他的subs属性用来存放了订阅他的观察者,也就是后面我们会说到的watcher。
在dep的订阅者数组中存放了Dep.target,让Dep.target订阅dep
那Dep.target是什么?他就是我们后面介绍的Watcher实例,为什么要放在Dep.target里呢?是因为getter函数并不能传参,dep可以通过闭包的形式放进去,那watcher可就不行了,watcher内部存放了a+b这个表达式,也是由watcher计算a+b的值,在计算前他会把自己放在一个公开的地方(Dep.target),然后计算a+b,从而触发表达式中所有遇到的依赖的getter,这些getter执行过程中会把Dep.target加到自己的订阅列表中。等整个表达式计算成功,Dep.target又恢复为null.这样就成功的让watcher分发到了对应的依赖的订阅者列表中,订阅到了自己的所有依赖。
上面setter代码里的dep.notify就负责完成数据变动时通知订阅者的功能
getter和setter存在的缺陷:只能监听到属性的更改,不能监听到属性的删除与添加。
Vue的解决办法是提供了响应式的api: vm.$set
/ vm.$delete
/ Vue.set
/
Vue.delete
/ 数组的 $set
/ 数组的 $remove
具体方法是为所有的对象和数组(只有这俩哥们才可能delete和新建属性),也创建一个dep,也完成收集依赖的过程。我们回到源码defineReactive再看一遍,在执行defineReactive(data,'a',{b:true})时,他首先创造了那个闭包在getter/setter中的dep,然后var childOb = observe(val),val是{b:true},那就会为这个对象new Observer(val),并放在val.ob上,而这个ob实例上存放了一个Dep实例。现在我们看到,有两个Dep实例,一个是闭包里的dep,一个是为{b:true}创建的ob上的这个dep。而:class="a"生成的watcher的求值过程中会触发到a的getter。
网友评论