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 & 總結
相關文章
相關標籤/搜索