前言
通過珠峯課程的學習來理解computed計算屬性的原理
那首先呢,先回顧一下vue的響應式數據原理
Vue 在初始化數據時,會給 data 中的所有屬性使用 Object.defineProperty 重新定義 setter 和 getter , 當頁面獲取到對應屬性時,會觸發 get 方法並進行依賴收集(收集當前組件的watcher) 如果屬性發生變化會通知相關依賴進行更新操作 。
其實呢,它們本質上沒有什麼區別,在前章分享響應式數據原理的時候我們就略帶過 computed 計算屬性,好,接着往下看:
響應式數據我們知道有個初始化 data 的方法叫做 initData
, 那麼計算屬性當然也有自己的初始化叫做initComputed
, 我們走進源碼:src/core/instance/state.js 169 行
function initComputed (vm: Component, computed: Object) { // 初始化計算屬性
const watchers = vm._computedWatchers = Object.create(null) // 初始化 計算屬性 watcher 列表
const isSSR = isServerRendering() // 判斷是否是服務器渲染
for (const key in computed) {
const userDef = computed[key] //注意:獲取用戶定義的方法
// 就是計算屬性 computed 裏邊定義的自變量函數 ↑
const getter = typeof userDef === 'function' ? userDef : userDef.get // 獲取
if (process.env.NODE_ENV !== 'production' && getter == null) { // 環境判斷
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) { // 非服務器渲染
watchers[key] = new Watcher( // 爲每一個計算屬性創建一個 watcher
vm,
getter || noop, // 將用戶定義的方法傳入
noop,
computedWatcherOptions // 注意:爲計算屬性options傳入 { lazy:true }
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef) // 重點注意: 將計算屬性定義到實例上 , 方法實體在下邊
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
}
}
}
}
export function defineComputed ( // vm 初始化的實例 ↑
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key) // 創建計算屬性的 getter 方法實體 ↓
: createGetterInvoker(userDef) // 調用計算屬性的 getter 方法實體 ↓
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key) // 創建計算屬性的 getter 方法實體 ↓
: createGetterInvoker(userDef.get) // 調用計算屬性的 getter 方法實體 ↓
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () { // 環境判斷
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition) // 響應式數據驅動更新
}
// 創建計算屬性 getter 方法
function createComputedGetter (key) {
return function computedGetter () { // 取值的時候回調用此方法
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) { // 做了一個dirty 實現了緩存的機制 , 計算屬性 調用對應的 evaluate ()
watcher.evaluate() // 如是計算屬性,則調用 evaluate() ;
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
// 調用計算屬性的 getter 方法
function createGetterInvoker (fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
總結
通過上邊的代碼我們可以瞭解到:initComputed
主要是爲每一個計算屬性創建一個watcher
並且在 options
裏邊傳入 lazy:true
計算屬性標識 , 然後再通過defineComputed
將計算屬性定義在實例上去進行一個響應式數據觀測 。
回顧
再次來到 watcher.js
中 src/observer/watcher.js 166行
,
update () { // 更新數據方法
/* istanbul ignore else */
if (this.lazy) { // 計算屬性 依賴的數據發生變化了 會讓計算屬性的watcher的dirty變成true
this.dirty = true
} else if (this.sync) { // 同步watcher
this.run()
} else {
queueWatcher(this) // 將watcher放入隊列
}
}
src/observer/watcher.js 212行
重點 , 重點 , 重點
evaluate () { // 計算屬性時,被調用自身的 get 方法
this.value = this.get()
this.dirty = false
}