看這篇之前,假如沒有看過之前的文章,移步拉到文章末端檢察之前的文章。
回憶
先捋一下,之前我們完成的 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
}
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 & 總結