在上一篇我們大概實現了,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
複製程式碼
但是上面的程式碼暴露了幾個問題
Dep
這個類將監聽屬性和處理依賴進行了解耦,但是卻沒有完全解耦,在觸發依賴的時候,還是得傳新舊值。- 上面程式碼中
Dep
中定義的removeSub
在程式碼中並沒有用到,因為Dep
的例項是在defineReactive
函式的作用域中,外部並不能直接呼叫,而刪除依賴肯定是在外部的環境中,也就是說即使我們將程式碼改成這樣,我們還是不能直接取刪除已經沒用的依賴。
Vue
中實現了一個 Watcher
的類來處理以上兩個問題,之後再說。
以下 ES6
語法下的 Dep
,Vue
原始碼中差不多就這樣
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
複製程式碼