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