VUE - part3 - Watcher

看这篇之前,如果没有看过之前的文章,可拉到文章末尾查看之前的文章。

前言

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

  1. 解耦不完全,需要传递参数
  2. 没有地方可以移除依赖

考虑问题

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

第二个问题其实反映出当前的 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 我们需要改造一下之前实现的 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 & 总结