Vue 透過 Observer
將普通物件加上 getters
和 setter
,使其變成響應式物件,
響應式物件與 getters
相關的邏輯就是做 依賴收集
那為什麼要進行依賴收集?
下面範例中,畫面並沒有用到 title3
,但若我們執行 this.title3 = 'D'
修改 title3
數據時,會觸發title3
的 setter
,導致畫面重新渲染,這樣母湯
new Vue({
template:
`<div>
<div>標題1</div>
<div>{{ title1 }}</div>
<div>標題2</div>
<div>{{ title2 }}</div>
</div>`,
data: {
title1: 'A',
title2: 'B',
title3: 'C'
}
});
當發生了改變 data 的行為,例如this.title3 = 'D'
,observe 的 set 方法會發現 data 中屬性改變,進而通知 dep 有 data 改變了,然後 dep 通知相關的 watcher,使其執行 watcher 的 updater function,完成畫面更新。
Dep
是 getter 依賴收集的核心,回顧一下 defineReactive 的 getter 邏輯
// src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
/*************************************************
實例化 Dep
**************************************************/
const dep = new Dep()
//...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
/*************************************************
get function 透過 dep.depend 做依賴收集
**************************************************/
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
// ...
})
}
Dep
class 定義了一個靜態屬性 target
,Dep.target
就是一個 Watcher
。subs
陣列是用來儲存 Watcher
,當 data 屬性值發生改變,在 subs
陣列裡的所有 Watcher
都會收到更新通知
// src/core/observer/dep.js
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}