實現 VUE 中 MVVM - step2 - Dep

undefined_er發表於2019-04-11

在上一篇我們大概實現了,Vue 中的依賴收集和觸發,但我們僅僅是將依賴維護在一個內建陣列中,這樣做雖然容易理解,但畢竟不好維護,為了更容易的維護這些依賴,我們來實現一個維護依賴的類。

確定功能

首先我們可以先確定這個類下的屬性,以及一些功能:

類下屬性:

  • target 函式,用於存放需要新增的依賴

例項下屬性及方法:

  • subs/Array 用於存放依賴
  • addSub/Function 用於新增依賴
  • removeSub/Function 用於移除依賴
  • notify/Function 用於執行依賴

實現

考慮到直接放在瀏覽器上執行,所以直接用 ES5 的類寫法。

let Dep = function(){

    // 例項屬性
    this.subs = []
    
    // 例項方法
    this.addSub = function(sub){
        this.subs.push(sub)
    }
    
    this.removeSub = function(sub){
        const index = this.subs.indexOf(item)
        if (index > -1) {
            this.subs.splice(index, 1)
        }
    }
    
    this.notify = function(newValue, oldVaule){
        this.subs.forEach(fnc=>fnc(newValue, oldVaule))
    }
}

// 類屬性
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)
            }
            return value
        },
        set: function(newValue){
            if(newValue != value){
                dep.notify(newValue, value)
            }
            value = newValue
        }
    })
}
複製程式碼

可以發現,之前我們用來存放依賴的陣列變成了一個依賴管理(Dep)的例項。同樣的,在取值時收集依賴,在設定值(當值發生變化)時觸發依賴。

由於依賴的處理由 Dep 的例項管理了,這裡僅僅呼叫一下相關方法即可。

接下來試一試效果:

let object = {}
defineReactive(object, 'test', 'test')
Dep.target = function(newValue, oldValue){
    console.log('我被新增進去了,新的值是:' + newValue)
}
object.test
// test

Dep.target = null
object.test = 'test2'
// 我被新增進去了,新的值是:test2

Dep.target = function(newValue, oldValue){
    console.log('新增第二個函式,新的值是:' + newValue)
}
object.test
// test

Dep.target = null
object.test = 'test3'
// 我被新增進去了,新的值是:test3
// 新增第二個函式,新的值是:test3
複製程式碼

但是上面的程式碼暴露了幾個問題

  1. Dep 這個類將監聽屬性和處理依賴進行了解耦,但是卻沒有完全解耦,在觸發依賴的時候,還是得傳新舊值。
  2. 上面程式碼中 Dep 中定義的 removeSub 在程式碼中並沒有用到,因為 Dep 的例項是在 defineReactive 函式的作用域中,外部並不能直接呼叫,而刪除依賴肯定是在外部的環境中,也就是說即使我們將程式碼改成這樣,我們還是不能直接取刪除已經沒用的依賴。

Vue 中實現了一個 Watcher 的類來處理以上兩個問題,之後再說。

以下 ES6 語法下的 DepVue 原始碼中差不多就這樣

class Dep {

    constructor() {
        this.subs = []
    }

    addSub(sub) {
        this.subs.push(sub)
    }

    removeSub(sub) {
        const index = this.subs.indexOf(item)
        if (index > -1) {
            this.subs.splice(index, 1)
        }
    }

    notify() {
        this.subs.forEach(fnc=>fnc(oldValue, newValue))
    }
}

Dep.target = null
複製程式碼

點選檢視相關程式碼

相關文章