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