VUE - MVVM - part10 - Computed

看這篇之前,假如沒有看過之前的文章,移步拉到文章末端檢察之前的文章。

回憶

先捋一下,之前我們完成的 Vue 類,主要有一下的功用:

  1. 屬性和要領的代辦 proxy
  2. 監聽屬性 watcher
  3. 事宜

關於比與如今的 Vue 中的數據處置懲罰,我們另有一些東西沒有完成:Computedpropsprovied/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

我們能夠發明:

  1. 盤算屬性是代辦到 Vue 實例上的一個屬性
  2. 第一次挪用時,挪用了 get 要領(有 ‘測試緩存’ 輸出),而第二次沒有輸出
  3. 當依靠發作轉變時,再次挪用了 get 要領

處理

第一點很好處理,運用 Object.defineProperty 代辦一下就 ok。
接下來看第二點和第三點,當依靠發作轉變時,值就會變化,這點和我們之前完成 Watcher 很像,盤算屬性的值就是 get 函數的返回值,在 Watcher 中我們一樣保留了監聽的值(watcher.value),而這個值是會依據依靠的變化而變化的(假如沒看過 Watcher 完成的同硯,去看下 step3step4),所以盤算屬性的 get 就是 Watchergetter

那末 Watchercallback 是啥?實在這裏基礎不須要 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 的臟搜檢機制

依據我們上面的剖析,而 ComputedWatcher 的一種完成,所以我們要完成一個不及時更新的 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 下的數據相干的內容就差不多了,在完成 propsprovied/inject 機制前,我們須要先完成父子組件,這也是下一步的內容。

點擊檢察相干代碼

系列文章地點

  1. VUE – MVVM – part1 – defineProperty
  2. VUE – MVVM – part2 – Dep
  3. VUE – MVVM – part3 – Watcher
  4. VUE – MVVM – part4 – 優化Watcher
  5. VUE – MVVM – part5 – Observe
  6. VUE – MVVM – part6 – Array
  7. VUE – MVVM – part7 – Event
  8. VUE – MVVM – part8 – 優化Event
  9. VUE – MVVM – part9 – Vue
  10. VUE – MVVM – part10 – Computed
  11. VUE – MVVM – part11 – Extend
  12. VUE – MVVM – part12 – props
  13. VUE – MVVM – part13 – inject & 總結
    原文作者:aco
    原文地址: https://segmentfault.com/a/1190000014702879
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞