摘要:了解之后我們來實現它,同樣的為了方便理解我寫成了一個類這里的一般是的實例將屬性代理到實例下的構造函數我們實現了代理屬性和更新計算屬性的值,同時依賴沒變化時,也是不會觸發的更新,解決了以上的個問題。
看這篇之前,如果沒有看過之前的文章,移步拉到文章末尾查看之前的文章。
回顧先捋一下,之前我們實現的 Vue 類,主要有一下的功能:
屬性和方法的代理 proxy
監聽屬性 watcher
事件
對于比與現在的 Vue 中的數據處理,我們還有一些東西沒有實現:Computed、props、provied/inject。
由于后兩者和子父組件有關,先放一放,我們先來實現 Computed 。
Computed在官方文檔中有這么一句話:
計算屬性的結果會被緩存,除非依賴的響應式屬性變化才會重新計算。
這也是計算屬性性能比使用方法來的好的原因所在。
ok 現在我們來實現它,我們先規定一下一個計算屬性的形式:
{ get: Function, set: Function }
官方給了我們兩種形式來寫 Computed ,看了一眼源碼,發現最終是處理成這種形式,所以我們先直接使用這種形式,之后再做統一化處理。
慣例我們通過測試代碼來看我們要實現什么功能:
let test = new Vue({ data() { return { firstName: "aco", lastName: "Yang" } }, computed: { computedValue: { get() { console.log("測試緩存") return this.firstName + " " + this.lastName } }, computedSet: { get() { return this.firstName + " " + this.lastName }, set(value) { let names = value.split(" ") this.firstName = names[0] this.lastName = names[1] } } } }) console.log(test.computedValue) // 測試緩存 // aco Yang console.log(test.computedValue) // acoYang (緩存成功,并沒有調用 get 函數) test.computedSet = "accco Yang" console.log(test.computedValue) // 測試緩存 (通過 set 使得依賴發生了變化) // accco Yang
我們可以發現:
計算屬性是代理到 Vue 實例上的一個屬性
第一次調用時,調用了 get 方法(有 ‘測試緩存’ 輸出),而第二次沒有輸出
當依賴發生改變時,再次調用了 get 方法
解決第一點很好解決,使用 Object.defineProperty 代理一下就 ok。
接下來看第二點和第三點,當依賴發生改變時,值就會變化,這點和我們之前實現 Watcher 很像,計算屬性的值就是 get 函數的返回值,在 Watcher 中我們同樣保存了監聽的值(watcher.value),而這個值是會根據依賴的變化而變化的(如果沒看過 Watcher 實現的同學,去看下 step3 和 step4),所以計算屬性的 get 就是 Watcher 的 getter。
那么 Watcher 的 callback 是啥?其實這里根本不需要 callback ,計算屬性僅僅需要當依賴發生變化時,保存的值發生變化。
ok 了解之后我們來實現它,同樣的為了方便理解我寫成了一個類:
function noop() { } let uid = 0 export default class Computed { constructor(key, option, ctx) { // 這里的 ctx 一般是 Vue 的實例 this.uid = uid++ this.key = key this.option = option this.ctx = ctx this._init() } _init() { let watcher = new Watcher( this.ctx, this.option.get || noop, noop ) // 將屬性代理到 Vue 實例下 Object.defineProperty(this.ctx, this.key, { enumerable: true, configurable: true, set: this.option.set || noop, get() { return watcher.value } }) } } // Vue 的構造函數 export class Vue extends Event { constructor(options) { super() this.uid = uid++ this._init(options) } _init(options) { let vm = this ... for (let key in options.computed) { new Computed(vm, key, options.computed[key]) } } }
我們實現了代理屬性 Object.defineProperty 和更新計算屬性的值,同時依賴沒變化時,也是不會觸發 Watcher 的更新,解決了以上的 3 個問題。
但是,試想一下,計算屬性真的需要實時去更新對應的值嗎?
首先我們知道,依賴的屬性發生了變化會導致計算屬性的變化,換句話說就是,當計算屬性發生變化了,data 下的屬性一定有一部分發生了變化,而 data 下屬性發生變化,會導致視圖的改變,所以計算屬性發生變化在去觸發視圖的變化是不必要的。
其次,我們不能確保計算屬性一定會用到。
而基于第一點,計算屬性是不必要去觸發視圖的變化的,所以計算屬性其實只要在獲取的時候更新對應的值即可。
Watcher 的臟檢查機制根據我們上面的分析,而 Computed 是 Watcher 的一種實現,所以我們要實現一個不實時更新的 Watcher。
在 Watcher 中我們實現值的更新是通過下面這段代碼:
update() { const value = this.getter.call(this.obj) const oldValue = this.value this.value = value this.cb.call(this.obj, value, oldValue) }
當依賴更新的時候,會去觸發這個函數,這個函數變更了 Watcher 實例保存的 value ,所以我們需要在這里做出改變,先看下偽代碼:
update() { if(/* 判斷這個 Watcher 需不需要實時更新 */){ // doSomething // 跳出 update return } const value = this.getter.call(this.obj) const oldValue = this.value this.value = value this.cb.call(this.obj, value, oldValue) }
這里的判斷是需要我們一開始就告訴 Watcher 的,所以同樣的我們需要修改 Watcher 的構造函數
constructor(object, getter, callback, options) { ··· if (options) { this.lazy = !!options.lazy } else { this.lazy = false } this.dirty = this.lazy }
我們給 Watcher 多傳遞一個 options 來傳遞一些配置信息。這里我們把不需要實時更新的 Watcher 叫做 lazy Watcher。同時設置一個標志(dirty)來標志這個 Watcher 是否需要更新,換個專業點的名稱是否需要進行臟檢查。
ok 接下來我們把上面的偽代碼實現下:
update() { // 如果是 lazy Watcher if (this.lazy) { // 需要進行臟檢查 this.dirty = true return } const value = this.getter.call(this.obj) const oldValue = this.value this.value = value this.cb.call(this.obj, value, oldValue) }
如果代碼走到 update 也就說明這個 Watcher 的依賴發生了變化,同時這是個 lazy Watcher ,那這個 Watcher 就需要進行臟檢查。
但是,上面代碼雖然標志了這個 Watcher ,但是 value 并沒有發生變化,我們需要專門寫一個函數去觸發變化。
/** * 臟檢查機制手動觸發更新函數 */ evaluate() { this.value = this.getter.call(this.obj) // 臟檢查機制觸發后,重置 dirty this.dirty = false }
查看完整的 Watcher 代碼
ok 接著我們來修改 Computed 的實現:
class Computed { constructor(ctx, key, option,) { this.uid = uid++ this.key = key this.option = option this.ctx = ctx this._init() } _init() { let watcher = new Watcher( this.ctx, this.option.get || noop, noop, // 告訴 Wather 來一個 lazy Watcher {lazy: true} ) Object.defineProperty(this.ctx, this.key, { enumerable: true, configurable: true, set: this.option.set || noop, get() { // 如果是 dirty watch 那就觸發臟檢查機制,更新值 if (watcher.dirty) { watcher.evaluate() } return watcher.value } }) } }
ok 測試一下
let test = new Vue({ data() { return { firstName: "aco", lastName: "Yang" } }, computed: { computedValue: { get() { console.log("測試緩存") return this.firstName + " " + this.lastName } }, computedSet: { get() { return this.firstName + " " + this.lastName }, set(value) { let names = value.split(" ") this.firstName = names[0] this.lastName = names[1] } } } }) // 測試緩存 (剛綁定 watcher 時會調用一次 get 進行依賴綁定) console.log("-------------") console.log(test.computedValue) // 測試緩存 // aco Yang console.log(test.computedValue) // acoYang (緩存成功,并沒有調用 get 函數) test.firstName = "acco" console.log(test.computedValue) // 測試緩存 (當依賴發生變化時,就會調用 get 函數) // acco Yang test.computedSet = "accco Yang" console.log(test.computedValue) // 測試緩存 (通過 set 使得依賴發生了變化) // accco Yang
到目前為止,單個 Vue 下的數據相關的內容就差不多了,在實現 props、provied/inject 機制前,我們需要先實現父子組件,這也是下一步的內容。
點擊查看相關代碼
系列文章地址VUE - MVVM - part1 - defineProperty
VUE - MVVM - part2 - Dep
VUE - MVVM - part3 - Watcher
VUE - MVVM - part4 - 優化Watcher
VUE - MVVM - part5 - Observe
VUE - MVVM - part6 - Array
VUE - MVVM - part7 - Event
VUE - MVVM - part8 - 優化Event
VUE - MVVM - part9 - Vue
VUE - MVVM - part10 - Computed
VUE - MVVM - part11 - Extend
VUE - MVVM - part12 - props
VUE - MVVM - part13 - inject & 總結
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107764.html
摘要:所以方法,是對默認進行擴展,從而實現擴展。這里我用了這個庫提供的合并方法,用來合并兩個對象,并不會修改原對象的內容。測試符合我們的預期,方法也就實現了,下一步,實現父子組件。系列文章地址優化優化總結 看這篇之前,如果沒有看過之前的文章,移步拉到文章末尾查看之前的文章。 組件的擴展 在 Vue 中有 extend 方法可以擴展 Vue 的實例,在上一步中,有一些實現是必須要通過子父組件才...
摘要:通過裝作這些變化,我們實現了從而到達了數據變化觸發函數的過程。于此同時,我們還實現了來擴展這個可響應的結構,讓這個對象擁有了觸發和響應事件的能力。最后,根據我們的實現,這是最終的產出,一個框架,了解一下系列文章地址優化優化總結 看這篇之前,如果沒有看過之前的文章,移步拉到文章末尾查看之前的文章。 provide / inject 在上一步我們實現了,父子組件,和 props 一樣 pr...
摘要:在中關于如何實現在網上可以搜出不少,在看了部分源碼后,梳理一下內容。換個說法,當我們取值的時候,函數自動幫我們添加了針對當前值的依賴,當這個值發生變化的時候,處理了這些依賴,比如說節點的變化。 在 VUE 中關于如何實現在網上可以搜出不少,在看了部分源碼后,梳理一下內容。 首先,我們需要了解一下 js 中的一個 API :Object.defineProperty(obj, prop,...
摘要:事件是什么在標準瀏覽器中,我們經常使用來為一個添加一個事件等。仔細看這些情況,歸結到代碼中,無非就是一個行為或情況的名稱,和一些列的動作,而在中動作就是,一系列的動作就是一個函數的集合。 看這篇之前,如果沒有看過之前的文章,可拉到文章末尾查看之前的文章。 事件是什么? 在標準瀏覽器中,我們經常使用:addEventListener 來為一個 DOM 添加一個事件(click、mouse...
摘要:看這篇之前,如果沒看過先移步看實現中。同樣的,在取值時收集依賴,在設置值當值發生變化時觸發依賴。中實現了一個的類來處理以上兩個問題,之后再說。以下語法下的,源碼中差不多就這樣點擊查看相關代碼系列文章地址優化優化總結 看這篇之前,如果沒看過 step1 先移步看 實現 VUE 中 MVVM - step1 - defineProperty。 在上一篇我們大概實現了,Vue 中的依賴收集和...
閱讀 2335·2021-11-15 11:38
閱讀 3544·2021-09-22 15:16
閱讀 1187·2021-09-10 11:11
閱讀 3156·2021-09-10 10:51
閱讀 2921·2019-08-30 15:56
閱讀 2774·2019-08-30 15:44
閱讀 3185·2019-08-28 18:28
閱讀 3525·2019-08-26 13:36