看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。
前言
在 step2
中,我们实现了一个管理依赖的 Dep
,但是仅仅使用这个类并不能完成我们想实现的功能,而且代码的解耦上也有点小问题。以下是在 step2
中最后说的几个问题:
- 解耦不完全,需要传递参数
- 没有地方可以移除依赖
考虑问题
第一个问题显示出来一个问题,由于我们的依赖是函数,为了函数的执行我们只能讲参数传进去,这个问题的根源在于我们的依赖是一个函数;
第二个问题其实反映出当前的 dep
实例只有在 defineReactive
中使用,而没有暴露出来,只要在外部有这个实例的引用,那么我们就能顺利的调用移除依赖了(removeSub
)。
解决第一个问题很简单,我们把某个属性的值、对应值变化时需要执行的函数抽象成一个对象,然后把这个对象当成是依赖,推入依赖管理中。
在第一个问题的基础上第二个问题就能解决了,我们只需要把 dep
的引用保存在依赖对象中就可以了。
当然我也是在看了 Vue
源码的基础上才有了上面的解决办法,这里不得不给尤大大赞一个。
Watcher 的实现
有了以上的考虑,那个依赖对象在 Vue
中就是 Watcher
。
let Watcher = function(object, key, callback){
this.obj = object
this.getter = key
this.cb = callback
this.dep = null
this.value = undefined
this.get = function(){
Dep.target = this
let value = this.obj[this.getter]
Dep.target = null
return value
}
this.update = function(){
const value = this.obj[this.getter]
const oldValue = this.value
this.value = value
this.cb.call(this.obj, value, oldValue)
}
this.addDep = function(dep) {
this.dep = dep
}
this.value = this.get()
}
上述代码实现了一个 Watcher
,为了方便起见我这里叫它监听。
该类的实例保存了需要监听的对象(object
),取值方法(key
),对应的回调(callback
),需要监听的值(value
),以及取值函数(get
)和触发函数(update
),这样我们就把依赖相关的所有内容保存在这个 Watcher
的实例中。
为了保存对 Dep
的引用,在 Watcher
中设置了 dep
,用于存放该监听被那个 Dep
给引用了。
由于在 Watcher
实例化的时候,我们已经对相应的值取了一次值,就是将以下代码放在在 Watcher
中
Dep.target = function(newValue, oldValue){
console.log('我被添加进去了,新的值是:' + newValue)
}
object.test
Dep.target = null
对应的代码为
this.get = function(){
Dep.target = this
let vaule = this.obj[this.getter]
Dep.target = null
return value
}
this.value = this.get()
所以在编写代码的时候,不用特地的去触发 get
添加依赖。
那么针对 Watcher
我们需要改造一下之前实现的 Dep
和 defineReactive
函数。
- 由于依赖变成了
Watcher
所以在Dep
中notify
应该改成Watcher
下的触发函数:update
- 由于
watcher
中存放了变量的状态,所以不需要在defineReactive
函数中传入参数
let Dep = function(){
this.subs = []
this.addSub = function(sub){
this.subs.push(sub)
}
this.removeSub = function(sub){
const index = this.subs.indexOf(sub)
if (index > -1) {
this.subs.splice(index, 1)
}
}
this.notify = function(){
// 修改触发方法
this.subs.forEach(watcher=>watcher.update())
}
}
Dep.target = null
let defineReactive = function(object, key, value){
let dep = new Dep()
Object.defineProperty(object, key, {
configurable: true,
enumerable: true,
get: function(){
if(Dep.target){
dep.addSub(Dep.target)
// 添加 watcher 对 dep 的引用
Dep.target.addDep(dep)
}
return value
},
set: function(newValue){
if(newValue != value){
value = newValue
// 不需要特地传入参数
dep.notify()
}
}
})
}
接下来我们来测试一下
let object = {}
defineReactive(object, 'test', 'test')
let watcher = new Watcher(object, 'test', function(newValue, oldValue){
console.log('作为 watcher 添加的第一个函数,很自豪。新值:' + newValue)
})
object.test = 'test2'
// 作为 watcher 添加的第一个函数,很自豪。新值:test2
let watcher2 = new Watcher(object, 'test', function(newValue, oldValue){
console.log('作为 watcher 添加的第二个函数,也很自豪。新值:' + newValue)
})
object.test = 'test3'
// 作为 watcher 添加的第一个函数,很自豪。新值:test3
// 作为 watcher 添加的第二个函数,也很自豪。新值:test3
// 接着我们来试一下删除依赖,把 watcher2 给删除
watcher2.dep.removeSub(watcher2)
object.test = 'test4'
// 作为 watcher 添加的第一个函数,很自豪。新值:test4
通过上面代码,我们成功解耦,用一个监听来处理某个属性的内容(oldValue
, newValue
, callback
),而且我们也能够去除 dep
中没用的依赖。
当然这个 Watcher 还是需要优化的,比如被多个 Dep
引用,这个就得存一个数组,之后继续优化。
系列文章地址
- VUE - MVVM - part1 - defineProperty
- VUE - MVVM - part2 - Dep
- VUE - MVVM - part3 - Watcher
- VUE - MVVM - part4 - 优化Watcher
- VUE - MVVM - part5 - Observe
- VUE - MVVM - part6 - Array
- VUE - MVVM - part7 - Event
- VUE - MVVM - part8 - 优化Event
- VUE - MVVM - part9 - Vue
- VUE - MVVM - part10 - Computed
- VUE - MVVM - part11 - Extend
- VUE - MVVM - part12 - props
- VUE - MVVM - part13 - inject & 总结
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。