概述
vue3中,computed
函數用于表示計算屬性,有惰性求值、響應式追蹤依賴的特點。本文將介紹computed
的實現原理以及其機制細節。
源碼解析
computed
計算屬性和computed
方法、ComputedRefImpl
類以及refreshComputed
方法有關。
computed
方法
computed
暴露給外部的就是computed
方法,其源碼實現如下:
function computed(getterOrOptions, debugOptions, isSSR = false) {let getter;let setter;if (shared.isFunction(getterOrOptions)) {getter = getterOrOptions;} else {getter = getterOrOptions.get;setter = getterOrOptions.set;}const cRef = new ComputedRefImpl(getter, setter, isSSR);return cRef;
}
computed
方法實現比較簡單,需要關注參數getterOrOptions
和isSSR
,isSSR
默認為false
,它在服務端渲染會傳值為true
。debugOptions
用以在開發環境調試。
computed
會先判斷getterOrOptions
是否是函數,若是函數,則將其賦值給getter
;當然getterOptions
也可以是一個包含get
和set
方法的對象。computed
方法返回的是ComputedRefImpl
實例,一般我們讀取計算屬性的值也是讀取它的返回值的.value
。
ComputedRefImpl
類
ComputedRefImpl
用于構造一個計算屬性。
ComputedRefImpl
的源碼實現如下:
class ComputedRefImpl {constructor(fn, setter, isSSR) {this.fn = getter; //計算函數this.setter = setter; // 設置函數(可選)this["_value"] = undefined; // 緩存的結果,計算屬性的值this.dep = new Dep(this); // 依賴收集器(收集依賴此計算屬性的副作用effect)this["__v_isRef"] = true; // 表示為ref類型this["__v_isReadonly"] = undefined; // 只讀標記this.deps = undefined; //當前計算屬性依賴的響應式集合對象鏈表頭this.depsTail = undefined; //鏈表尾this.flags = 16; //狀態標記this.globalVersion = globalVersion - 1;// 全局版本號,用于臟檢查this.isSSR = undefined; //服務端渲染標記this.next = undefined; //用于在effect鏈表中指向下一個節點this.effect = this; // 指向自身this["__V_isReadonly"] = !setter; //若無setter,則表示計算屬性是只讀的this.isSSR = isSSR;//ssr標記賦值}// 依賴變更調用notify() {this.flags |= 16;if (!(this.flags & 8) && activeSub !== this) {batch(this, true);return true;}}// 計算屬性值訪問器get value() {const link = this.dep.track()refreshComputed(this);if (link) {link.version = this.dep.version;}return this._value;}// 計算屬性值設置器set value(newValue) {if (this.setter) {this.setter(newValue)} else {warn("Write operation failed: computed value is readonly");}}
}
ComputedRefImpl
中除了在構造器中定義了相關屬性外,就是包含兩個屬性訪問器函數和一個notify
方法
notify
當計算屬性依賴的響應式值發生變化時,會調用notify
方法.notify
方法會先設置this.flags
標志位的值,將其第4位置為1,表示有更新請求;然后判斷標志位的第3位是否為1并且當前激活訂閱(副作用)是不是自身,若條件滿足,則調用batch
方法,將該計算屬性添加到更新隊列中,進行批量更新,最后返回true
,表示更新已調度;若不滿足條件,則返回false
,表示更新被跳過。
getter
getter
屬性訪問器,會在讀取計算屬性的值時觸發。先進行依賴收集,追蹤當前正在運行的effect
,然后調用refreshComputed
方法進行有條件性的重新計算,若當前計算屬性被其他effect
依賴,則更新依賴的版本,最后返回this._value
。
setter
setter
屬性設置,一般情況下計算屬性只是只讀的,若this.setter
方法存在,則可以調用該方法設置計算屬性的值。
refreshComputed
方法
refreshComputed
方法就是用于進行刷新計算屬性的值,滿足條件就重新進行計算,得到最新的計算屬性的值。
refreshComputed
的源碼實現如下:
function refreshComputed(computed) {// 檢查更新條件if (computed.flags & 4 && !(computed.flags & 16)) {return;}// 清除pending狀態computed.flags & =-17;// 全局版本校驗 避免重復計算if (computed.globalVersion === globalVersion) {return;}computed.globalVersion = globalVersion;// 緩存有效性檢查 if (!computed.isSSR && computed.flags & 128 && (!computed.deps && !computed._dirty || !isDirty(computed))) {return;}// 標記計算狀態computed.flags |= 2;const dep = computed.dep;const prevSub = activeSub;const prevShouldTrack = shouldTrack;activeSub = computed;shouldTrack = true;try {// 依賴收集準備prepareDeps(computed);// 執行計算函數const value = computed.fn(computed._value);// 值變化檢測if (dep.version === 0 || hasChanged(value, computed._value)) {computed.flags |= 128; // 標記validcomputed._value = value; dep.version++; // 觸發依賴更新}} catch (err) {dep.version++;throw err;} finally {activeSub = prevSub;shouldTrack = prevShouldTrack;cleanupDep(computed); // 清理過期依賴computed.flags &= -3; // 清除computing狀態}
}
refreshComputed
方法會先檢查flags
的值,若是被移除或者沒有更新請求,則直接返回;然后修改flags
狀態,清除pending
狀態;比較全局版本號和計算屬性的版本號,若二者一樣,則返回,避免是在計算屬性中修改了響應式屬性引起的重新計算;修改響應式的版本號;然后做緩存有效性的檢查;若是臟數據,則返回;再次標記flags
狀態,表示是計算中;將當前effect
計算屬性切換為activeSub
,修改shouldTrack
為true
,調用prepareDeps
進行依賴收集,然后執行計算屬性的fn
,即傳入computed
的參數函數,得到新的value
,比較計算屬性的值是否發生改變,賦值this._value
,并將其依賴dep
的版本遞增,如此會觸發依賴該計算屬性的副作用effect
更新;最后恢復activeSub
和shouldTrack
,清理過期依賴以及清除計算狀態。
refreshComputed
是計算屬性computed
的核心方法,依據一些規則判斷需要執行fn
,獲取最新的value
以及觸發相關依賴。