1弧械、提出問(wèn)題
Computed 計(jì)算屬性是 Vue 中常用的一個(gè)功能,但你理解它是怎么工作的嗎?
拿官網(wǎng)簡(jiǎn)單的例子來(lái)看一下:
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
const vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
- 計(jì)算屬性如何與屬性建立依賴關(guān)系?
- 屬性發(fā)生變化又如何通知到計(jì)算屬性重新計(jì)算见剩?
2、計(jì)算屬性的原理
- data 屬性初始化 getter setter
// 這里開(kāi)始轉(zhuǎn)換 data 的 getter setter扫俺,原始值已存入到 __ob__ 屬性中
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 判斷是否處于依賴收集狀態(tài)
if (Dep.target) {
// 建立依賴關(guān)系
dep.depend()
...
}
return value
},
set: function reactiveSetter (newVal) {
...
// 依賴發(fā)生變化苍苞,通知到計(jì)算屬性重新計(jì)算
dep.notify()
}
})
- computed 計(jì)算屬性初始化
// 初始化計(jì)算屬性
function initComputed (vm: Component, computed: Object) {
...
// 遍歷 computed 計(jì)算屬性
for (const key in computed) {
...
// 創(chuàng)建 Watcher 實(shí)例
// create internal watcher for the computed property
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
// 創(chuàng)建屬性 vm.reversedMessage,并將提供的函數(shù)將用作屬性 vm.reversedMessage 的 getter
// 最終 computed 與 data 會(huì)一起混合到 vm 下狼纬,所以當(dāng) computed 與 data 存在重名屬性時(shí)會(huì)拋出警告
defineComputed(vm, key, userDef)
...
}
}
export function defineComputed (target: any, key: string, userDef: Object | Function) {
...
創(chuàng)建 get set 方法
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
...
// 創(chuàng)建屬性 vm.reversedMessage羹呵,并初始化 getter setter
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
// watcher 暴露 evaluate 方法用于取值操作
watcher.evaluate()
}
// 同第1步,判斷是否處于依賴收集狀態(tài)
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
- 無(wú)論是屬性還是計(jì)算屬性疗琉,都會(huì)生成一個(gè)對(duì)應(yīng)的 watcher 實(shí)例
// 當(dāng)通過(guò) vm.reversedMessage 獲取計(jì)算屬性時(shí)担巩,就會(huì)進(jìn)到這個(gè) getter 方法
get () {
// this 指的是 watcher 實(shí)例
// 將當(dāng)前 watcher 實(shí)例暫存到 Dep.target,這就表示開(kāi)啟了依賴收集任務(wù)
pushTarget(this)
let value
const vm = this.vm
try {
// 在執(zhí)行 vm.reversedMessage 的函調(diào)函數(shù)時(shí)没炒,會(huì)觸發(fā)屬性(步驟1)和計(jì)算屬性(步驟2)的 getter
在這個(gè)執(zhí)行過(guò)程中,就可以收集到 vm.reversedMessage 的依賴了
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
// 結(jié)束依賴收集任務(wù)
popTarget()
this.cleanupDeps()
}
return value
}
3犯戏、結(jié)論
- 當(dāng)組件初始化的時(shí)候送火,computed和data會(huì)分別建立各自的響應(yīng)系統(tǒng),Observer遍歷data中每個(gè)屬性設(shè)置get/set數(shù)據(jù)攔截
- 初始化computed會(huì)調(diào)用initComputed函數(shù)
- 注冊(cè)一個(gè)watcher實(shí)例先匪,并在其內(nèi)實(shí)例化一個(gè)Dep消息訂閱器用作后續(xù)收集依賴(比如渲染函數(shù)的watcher或者其他觀察該計(jì)算屬性變化的watcher)
- 調(diào)用計(jì)算屬性時(shí)會(huì)觸發(fā)其Object.defineProperty的get訪問(wèn)器函數(shù)
- 調(diào)用watcher.depend()方法向自身的消息訂閱器dep的subs中添加其他屬性的watcher
- 調(diào)用watcher的evaluate方法(進(jìn)而調(diào)用watcher的get方法)讓自身成為其他watcher的消息訂閱器的訂閱者种吸,首先將watcher賦給Dep.target,然后執(zhí)行g(shù)etter求值函數(shù)呀非,當(dāng)訪問(wèn)求值函數(shù)里面的屬性(比如來(lái)自data坚俗、props或其他computed)時(shí),會(huì)同樣觸發(fā)它們的get訪問(wèn)器函數(shù)從而將該計(jì)算屬性的watcher添加到求值函數(shù)中屬性的watcher的消息訂閱器dep中岸裙,當(dāng)這些操作完成猖败,最后關(guān)閉Dep.target賦為null并返回求值函數(shù)結(jié)果。
- 當(dāng)某個(gè)屬性發(fā)生變化降允,觸發(fā)set攔截函數(shù)恩闻,然后調(diào)用自身消息訂閱器dep的notify方法,遍歷當(dāng)前dep中保存著所有訂閱者wathcer的subs數(shù)組剧董,并逐個(gè)調(diào)用watcher的 update方法幢尚,完成響應(yīng)更新。