看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。
回顾
首先我们思考一下截止当前,我们都做了什么
- 通过
defineReactive
这个函数,实现了对于数据取值和设置的监听 - 通过
Dep
类,实现了依赖的管理 - 通过
Watcher
类,抽象出了对象下某个属性的依赖,以及属性变换的callBack
发现问题
对比 Vue
的 MVVM
(先把视图层的渲染抽象成一个函数),我们仅仅是实现了一些基础性的东西。还有很大的区别,比如
- 我们的
Watcher
仅仅是抽象了对象下的单一属性,而一般视图层的渲染是涉及多个属性的,而这些属性的变化是同一个渲染函数(也就是Vue
中编译模板字符串最终生成的函数)。 - 通过第一点,我们可以得知,对象下的某几个属性是拥有同一个
Watcher
的,换句话说就是,多个Dep
依赖与同一个Watcher
,那么Watcher
中该如何保存这些Dep
,因为按照我们的实现,都一个Watcher
中仅仅保持一个Dep
解决问题
问题1
先让我们想想,我们是如何把依赖注入到 Dep
中的
通过取值触发 defineProperty
中的 get
,然后添加依赖
换句话说就是,我只要取过对应属性的值,那么就可以添加依赖。
看到之前 Watcher
的实现:
this.get = function () {
Dep.target = this
let value = this.getter.call(object)
Dep.target = null
return value
}
这段代码就实现了添加相应属性的依赖,归根到底是这段起了作用
let value = this.obj[this.getter]
这里触发了对应属性的 get
,那好针对第一个问题,我们只要在这里触发多个属性的 get
即可,至于要触发那些属性,我们交由调用者来控制,顺理成章的这里应该是一个函数。考虑之后便有了以下代码
let Watcher = function (object, getter, callback) {
this.obj = object
// 这里的 getter 应该是一个函数
this.getter = getter
this.cb = callback
this.dep = null
this.value = undefined
this.get = function () {
Dep.target = this
// 将取值方式改成函数调用
let value = this.getter.call(object)
Dep.target = null
return value
}
this.update = function () {
const value = this.getter.call(object)
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()
}
问题二
问题二其实很简单,既然要保存多个 dep 我们把保存的值声明成一个数组即可
let Watcher = function (object, getter, callback) {
this.obj = object
this.getter = getter
this.cb = callback
// 声明成数组
this.deps = []
this.value = undefined
this.get = function () {
Dep.target = this
let value = this.getter.call(object)
Dep.target = null
return value
}
this.update = function () {
const value = this.getter.call(object)
const oldValue = this.value
this.value = value
this.cb.call(this.obj, value, oldValue)
}
this.addDep = function (dep) {
// 将 dep 推入数组中
this.deps.push(dep)
}
this.value = this.get()
}
为了方便取消这个 Watcher
,我们在添加一个函数,用于取消所有 Dep
对 Watcher
的依赖,所以最终 Watcher
的代码如下:
let Watcher = function (object, getter, callback) {
this.obj = object
this.getter = getter
this.cb = callback
this.deps = []
this.value = undefined
this.get = function () {
Dep.target = this
let value = this.getter.call(object)
Dep.target = null
return value
}
this.update = function () {
const value = this.getter.call(object)
const oldValue = this.value
this.value = value
this.cb.call(this.obj, value, oldValue)
}
this.addDep = function (dep) {
this.deps.push(dep)
}
// 新添加的取消依赖的方法
this.teardown = function () {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.deps = []
}
this.value = this.get()
}
测试
我们仅仅优化了 Watcher
的实现,其他的代码并没有发生变化
let object = {}
defineReactive(object, 'num1', 2)
defineReactive(object, 'num2', 4)
let watcher = new Watcher(object, function () {
return this.num1 + this.num2
}, function (newValue, oldValue) {
console.log(`这是一个监听函数,${object.num1} + ${object.num2} = ${newValue}`)
})
object.num1 = 3
// 这是一个监听函数,3 + 4 = 7
object.num2 = 10
// 这是一个监听函数,3 + 10 = 13
let watcher2 = new Watcher(object, function () {
return this.num1 * this.num2
}, function (newValue, oldValue) {
console.log(`这是一个监听函数,${object.num1} * ${object.num2} = ${newValue}`)
})
object.num1 = 4
// 这是一个监听函数,4 + 10 = 14
// 这是一个监听函数,4 * 10 = 40
object.num2 = 11
// 这是一个监听函数,4 + 11 = 15
// 这是一个监听函数,4 * 11 = 44
// 测试取消
watcher2.teardown()
object.num1 = 5
// 这是一个监听函数,5 + 11 = 16
object.num2 = 12
// 这是一个监听函数,5 + 12 = 17
这就实现了对于多个属性设置同一个监听,当监听函数中的依赖属性发生变化时,自动执行了相应的函数。
关于 Vue
中的 MVVM
的实现 ,差不多也就这样了,当然这仅仅是基础的实现,而且视图层层渲染抽象成一个函数。
不同于 Vue
中的实现,这里少了很多各种标记和应用标记的过程。
这些会增加理解难度,之后有用到再说,实现完整的 MVVM
还需要对数组进行特殊的处理,因为数组是不能用 Object.defineProperty
来处理索引值的,这个也之后再说。
系列文章地址
- 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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。