看這篇之前,如果沒看過之前的文章先移步看
- 簡單實現 VUE 中 MVVM – step1 – defineProperty
- 簡單實現 VUE 中 MVVM – step2 – Dep
- 簡單實現 VUE 中 MVVM – step3 – Watcher
回顧
首先我們思考一下截止當前,我們都做了什麼
- 通過
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來處理索引值的,這個也之後再說。
更多內容,可以訪問http://blog.acohome.cn