VUE - MVVM - part3 - Watcher

看这篇以前,若是没有看过以前的文章,可拉到文章末尾查看以前的文章。vue

前言

step2 中,咱们实现了一个管理依赖的 Dep ,可是仅仅使用这个类并不能完成咱们想实现的功能,并且代码的解耦上也有点小问题。如下是在 step2 中最后说的几个问题:git

  1. 解耦不彻底,须要传递参数
  2. 没有地方能够移除依赖

考虑问题

第一个问题显示出来一个问题,因为咱们的依赖是函数,为了函数的执行咱们只能讲参数传进去,这个问题的根源在于咱们的依赖是一个函数;github

第二个问题其实反映出当前的 dep 实例只有在 defineReactive 中使用,而没有暴露出来,只要在外部有这个实例的引用,那么咱们就能顺利的调用移除依赖了(removeSub)。数组

解决第一个问题很简单,咱们把某个属性的值、对应值变化时须要执行的函数抽象成一个对象,而后把这个对象当成是依赖,推入依赖管理中。函数

在第一个问题的基础上第二个问题就能解决了,咱们只须要把 dep 的引用保存在依赖对象中就能够了。测试

固然我也是在看了 Vue 源码的基础上才有了上面的解决办法,这里不得不给尤大大赞一个。优化

Watcher 的实现

有了以上的考虑,那个依赖对象在 Vue 中就是 Watcherthis

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 ,为了方便起见我这里叫它监听。code

该类的实例保存了须要监听的对象(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 咱们须要改造一下以前实现的 DepdefineReactive 函数。

  1. 因为依赖变成了 Watcher 因此在 Depnotify 应该改为 Watcher 下的触发函数:update
  2. 因为 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 引用,这个就得存一个数组,以后继续优化。

点击查看相关代码

系列文章地址

  1. VUE - MVVM - part1 - defineProperty
  2. VUE - MVVM - part2 - Dep
  3. VUE - MVVM - part3 - Watcher
  4. VUE - MVVM - part4 - 优化Watcher
  5. VUE - MVVM - part5 - Observe
  6. VUE - MVVM - part6 - Array
  7. VUE - MVVM - part7 - Event
  8. VUE - MVVM - part8 - 优化Event
  9. VUE - MVVM - part9 - Vue
  10. VUE - MVVM - part10 - Computed
  11. VUE - MVVM - part11 - Extend
  12. VUE - MVVM - part12 - props
  13. VUE - MVVM - part13 - inject & 总结
相关文章
相关标签/搜索