開始
聲明一個對象man,可以視爲vue中的data
let man = {
height: 180,
weight: 70,
wealth: 100000000
}
添加Observer
作用在於將參數對象的屬性變爲響應式,只要對象的屬性被讀取或者被修改都能觀察到。然後新建一個Observer實例,將man作爲參數扔進去。這裏的proxyData是將man的屬性代理到以man爲參數的Observer實例上去。
class Observer {
constructor(obj) {
this.walk(obj)
}
walk(obj) {
Object.keys(obj).forEach(prop => {
this[prop] = obj[prop]
this.proxyData(obj, prop)
this.defineReactive(this, prop, obj[prop])
})
}
proxyData(obj, prop) {
let _this = this
Object.defineProperty(obj, prop, {
get() {
return _this[prop]
},
set(newVal) {
_this[prop] = newVal
}
})
}
defineReactive(obj, prop, val) {
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} - 被讀取!`)
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} - 被修改!`)
}
})
}
}
new Observer(man)
這時打印一下man
現在man的屬性都是由Observer實例所對應的屬性的getter來返回,只有在查看時會被觸發
對man的屬性進行修改也會觸發實例對應屬性的setter
添加Watcher
現在的Watcher有點像vue中的computed,實際上就是定義一個計算屬性,這個計算屬性依賴於前面man中的某些屬性,由他們計算而得。
class Watcher {
constructor(obj, prop, computed) {
this.getVal(obj, prop, computed)
}
getVal(obj, prop, computed) {
Object.defineProperty(obj, prop, {
get() {
console.log(`computed屬性 - ${prop}被讀取!`)
return computed()
},
set() {
console.error('計算屬性不可被修改!')
}
})
}
}
new Watcher(man, 'strength', () => {
let {height, weight} = man
if (height > 160 && weight > 70) return 'strong'
return 'weak'
})
看起來沒什麼問題,所依賴的屬性如果變了,計算屬性只要再被查看(get方法)一次就可以更新了。但vue中的視圖渲染是實時的,視圖層依賴於數據層,數據變化了,視圖層也會跟着變化,不需要手動更新。類比到這個例子就是計算屬性如何才能在其所依賴的屬性發生變化時被通知從而觸發應有的事件?
這時我們先給Watcher加多一個callback,用於處理當依賴的數據被修改時,我這個計算屬性該怎麼響應
比如當依賴被修改時,我們就把這個計算屬性的值打印出來
class Watcher {
constructor(obj, prop, computed, callback) {
this.getVal(obj, prop, computed, callback)
}
new Watcher(man, 'strength', () => {
let {height, weight} = man
if (height > 160 && weight > 70) return 'strong'
return 'weak'
}, () => {
console.log(`i am so ${man.strength} !`)
})
一切都準備好了,接下來就是該如何實現?
我們先看下Watcher中getVal這個方法
getVal(obj, prop, computed, callback) {
Object.defineProperty(obj, prop, {
get() {
console.log(`computed屬性 - ${prop}被讀取!`)
return computed()
},
set() {
console.error('計算屬性不可被修改!')
}
})
}
當我們查看計算屬性時,會調用computed這個方法,相當於查看了其所依賴的height和weight屬性,而在上面我們已經讓man的所有屬性都擁有了get方法,即他們被查看時我們是不是可以把callback塞給他們?
這時候我們引進一個橋樑,來連接Watcher和Observer。
添加Dep
Dep的用處在於當某一個屬性(以下稱‘自己’)被依賴了,將依賴自己的粉絲(們)--也就是Watcher(s),收集起來,假如自己發生了變化,能夠及時通知粉絲們。
class Dep {
constructor() {
this.deps = []
}
getDeps() {
if (!Dep.target || this.deps.includes(Dep.target)) return
console.log('依賴添加', Dep.target)
this.deps.push(Dep.target)
}
notify() {
this.deps.forEach(dep => {
dep()
})
}
}
這裏的Dep.target就是前面所說的callback方法了。這時我們改一下Watcher中的getVal
getVal(obj, prop, computed, callback) {
Object.defineProperty(obj, prop, {
get() {
Dep.target = callback
console.log(`computed屬性 - ${prop}被讀取!`)
return computed()
},
set() {
console.error('計算屬性不可被修改!')
}
})
}
在計算屬性被查看時,將callback賦值給Dep.target,接下來就會調用其所依賴屬性的getter,我們只要在getter裏把callback給收集起來就行了。接下來修改依賴屬性的getter方法。
defineReactive(obj, prop, val) {
let dep = new Dep()
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} - 被讀取!`)
dep.getDeps() // 依賴收集
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} - 被修改!`)
}
})
}
這時watcher的callback都被依賴屬性給收集起來了,當依賴屬性發生變化時只要去運行這些callback就可以了。接下來就是修改依賴屬性的setter方法。
defineReactive(obj, prop, val) {
let dep = new Dep()
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} - 被讀取!`)
dep.getDeps()
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} - 被修改!`)
dep.notify() // 運行所有callback
}
})
}
運行看看
我們加多一個Watcher試試
new Watcher(man, 'isGreat', () => {
let {height, weight, wealth} = man
if (height > 180 && weight > 70 && wealth > 100000) return 'Great!'
return 'not good enough ...'
}, () => {
console.log(`they say i am ${man.isGreat}`)
})
這就是vue中的一個依賴對應多個Watcher