Computed 計算屬性是 Vue 中經常使用的一個功能,咱們今天來講一下他的執行過長react
拿官網簡單的例子來看一下:express
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> var 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('') } } })
data 屬性初始化 getter setter:ide
// src/observer/index.js // 這裏開始轉換 data 的 getter setter,原始值已存入到 __ob__ 屬性中 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 判斷是否處於依賴收集狀態 if (Dep.target) { // 創建依賴關係 dep.depend() ... } return value }, set: function reactiveSetter (newVal) { ... // 依賴發生變化,通知到計算屬性從新計算 dep.notify() } })
computed 計算屬性初始化函數
// src/core/instance/state.js // 初始化計算屬性 function initComputed (vm: Component, computed: Object) { ... // 遍歷 computed 計算屬性 for (const key in computed) { ... // 建立 Watcher 實例 // create internal watcher for the computed property. watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions) // 建立屬性 vm.reversedMessage,並將提供的函數將用做屬性 vm.reversedMessage 的 getter, // 最終 computed 與 data 會一塊兒混合到 vm 下,因此當 computed 與 data 存在重名屬性時會拋出警告 defineComputed(vm, key, userDef) ... } } export function defineComputed (target: any, key: string, userDef: Object | Function) { ... // 建立 get set 方法 sharedPropertyDefinition.get = createComputedGetter(key) sharedPropertyDefinition.set = noop ... // 建立屬性 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步,判斷是否處於依賴收集狀態 if (Dep.target) { watcher.depend() } return watcher.value } } }
不管是屬性仍是計算屬性,都會生成一個對應的 watcher 實例。oop
// src/core/observer/watcher.js // 當經過 vm.reversedMessage 獲取計算屬性時,就會進到這個 getter 方法 get () { // this 指的是 watcher 實例 // 將當前 watcher 實例暫存到 Dep.target,這就表示開啓了依賴收集任務 pushTarget(this) let value const vm = this.vm try { // 在執行 vm.reversedMessage 的函調函數時,會觸發屬性(步驟1)和計算屬性(步驟2)的 getter // 在這個執行過程當中,就能夠收集到 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) } // 結束依賴收集任務 popTarget() this.cleanupDeps() } return value }
上面多出提到了 dep.depend, dep.notify, Dep.target,那麼 Dep 到底是什麼呢?源碼分析
Dep 的代碼短小精悍,但卻承擔着很是重要的依賴收集環節。ui
// 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++) { // 更新 watcher 的值,與 watcher.evaluate() 相似, // 但 update 是給依賴變化時使用的,包含對 watch 的處理 subs[i].update() } } } // 當首次計算 computed 屬性的值時,Dep 將會在計算期間對依賴進行收集 Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { // 在一次依賴收集期間,若是有其餘依賴收集任務開始(好比:當前 computed 計算屬性嵌套其餘 computed 計算屬性), // 那麼將會把當前 target 暫存到 targetStack,先進行其餘 target 的依賴收集, if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { // 當嵌套的依賴收集任務完成後,將 target 恢復爲上一層的 Watcher,並繼續作依賴收集 Dep.target = targetStack.pop() }
1. data 屬性初始化 getter setterthis
2. computed 計算屬性初始化,提供的函數將用做屬性 vm.reversedMessage 的 getterlua
3. 當首次獲取 reversedMessage 計算屬性的值時,Dep 開始依賴收集server
4. 在執行 message getter 方法時,若是 Dep 處於依賴收集狀態,則斷定 message 爲 reversedMessage 的依賴,並創建依賴關係
5. 當 message 發生變化時,根據依賴關係,觸發 reverseMessage 的從新計算