Vueには算出プロパティという、関数が依存するデータの変更を検知して関数を再実行してビューに反映させる機能があります。
算出プロパティを使ったコードの例
<div id="el">
<!-- 算出プロパティを使用しているところ。foobarと表示される -->
{{ computedValue }}
</div>
new Vue({
el: "#el",
data: function () {
return {value: 'foo'}
},
computed: {
// これが算出プロパティの定義
function computedValue () {
return this.value + 'bar'
}
}
})
算出プロパティである computedValue() が this.value に依存していて、this.value = 'hoge' のようにして値を変更するとcomputedValueは再計算され、foobarだった表示がfoohogeになります。
データを変更するとすべての算出プロパティが再計算されるのではなく、本当に依存している算出プロパティだけが再計算されるのです。
なぜこんなことが可能なのか理解できなかったのでソースを追って調べてみました。
思いつかなかっただけですごく簡単でした。(最初はコードをパースしないと分からなくない?と思いましたが)
算出プロパティが実行される時に依存関係を発見する仕組みになっていました。
算出プロパティはWatcherというクラスで表されます。
また、その他のVueで管理されるデータはObserverというクラスで表されます。
このWatcher、Observerと依存を管理するDepというクラスを読むと仕組みが理解できます。
src/core/observer/index.js (Observerはここに定義)
src/core/observer/watcher.js
src/core/observer/dep.js
算出プロパティが呼ばれるとWatcher#get()という関数が呼ばれます。
この関数が呼ばれると、自分自身を依存元を示すオブジェクトとしてマークしてから、実際の値を計算する関数を呼び出します。
値を計算するために依存先データのgetterが呼ばれるので、このときにマークしてあったオブジェクトを依存元(自分の変更を通知しなければならない対象)として追加します。
これで依存関係を正しく把握することができます。
すごーくおおざっぱに擬似的なコードを書くとこんな感じになっています。
class Dep {
constructor () {
this.deps = []
}
add (target) {
this.deps.push(target)
}
notify () {
// 変更したことを this.deps に含まれるオブジェクトに伝える
}
}
let depTarget = null
class Watcher {
get () {
depTarget = this
this.getter.call() // ここで実際の計算を行う
depTarget = null
}
}
class Observer {
constructor (getter) {
this.dep = new Dep()
this.getter = getter
}
// 計算の中でデータを参照すると呼ばれる
get () {
this.dep.add(depTarget) // 依存対象を追加する
return this.getter.call() // 実際の値を取得
}
set () {
this.dep.notify() // 自分自身の変更を伝える
}
}
わかってしまえば簡単なものですね。