看這篇以前,若是沒有看過以前的文章,移步拉到文章末尾查看以前的文章。vue
先捋一下,以前咱們實現的 Vue
類,主要有一下的功能:git
proxy
watcher
對於比與如今的 Vue
中的數據處理,咱們還有一些東西沒有實現:Computed
、props
、provied/inject
。github
因爲後二者和子父組件有關,先放一放,咱們先來實現 Computed
。緩存
在官方文檔中有這麼一句話:函數
計算屬性的結果會被緩存,除非依賴的響應式屬性變化纔會從新計算。
這也是計算屬性性能比使用方法來的好的緣由所在。oop
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
咱們能夠發現:ui
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
下屬性發生變化,會致使視圖的改變,因此計算屬性發生變化在去觸發視圖的變化是沒必要要的。
其次,咱們不能確保計算屬性必定會用到。
而基於第一點,計算屬性是沒必要要去觸發視圖的變化的,因此計算屬性其實只要在獲取的時候更新對應的值便可。
根據咱們上面的分析,而 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 }
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
機制前,咱們須要先實現父子組件,這也是下一步的內容。