Vue.js の observe あたり雑に追う
(まとまりのなさがひどすぎてアレで放置してたらもう1.0.7になってますはい)
もう 1.0.4 になっとるやないけ
1.0.4 見ますはい。
データバインディングをどう実現しているのかが気になるのでその辺。
データバインディングといえば Observer ということで、 srsc/observer あたりを見てみようかと。
一年くらい前の記事だけど、 JavaScript フレームワークがデータバインディングを実現する4通りの手法 の中で、 Vue.js は「モデル書き換え方式」として紹介されている。
記事の中にもあるように、モデルを書き換えまくることで Vue.js はデータバインディングを実現してる。
おかげで特定のクラスのインスタンスを使わないといけないとかいうことはなく、意識せず使える。
ここから引用すると、 http://vuejs.org/guide/instance.html#Properties_and_Methods
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a === data.a // -> true
// setting the property also affects original data
vm.a = 2
data.a // -> 2
// ... and vice-versa
data.a = 3
vm.a // -> 3
console.log を仕込んでみると書き換えられている様子がわかる。
// インスタンスにセットする前
console.log(data) // -> { a: 1 }
// 後
console.log(data) // -> { a: [Getter/Setter] }
実際に取得できる値に変化はないけど、中身が書き換えられていることがわかる。
[Getter/Setter] という関数(?)になっちゃってる。
Observer は https://github.com/vuejs/vue/blob/1.0.4/src/observer/index.js#L6L29 ここ。
コメントに、依存関係を収集したり(?)更新をディスパッチするために対象のオブジェクトのプロパティを geter/setter に変換するよ、って書いてある
- Observer class that are attached to each observed
- object. Once attached, the observer converts target
- object's property keys into getter/setters that
- collect dependencies and dispatches updates.
で、 Dep というオブジェクトが出てくるが、名前的にきっと依存関係を表すものかな。
_.define というので __ob__ というプロパティを対象のオブジェクトに生やして自分自身をそこに入れてる。
_.define は util で、 Object.defineProperty のラッパー
対象が配列なら何とかして(protoAugmentかcopyAugment)配列用の便利関数的なものを生やしまくる。
そして walk で実際に convert していく様子
convert は defineReactive を呼び出す。
defineReactive によって、オブジェクトに getter/setter が定義されてる。
getter のほうは、値を返す前に dep の depend をたくさん呼んでるが、これはなんだろう。
setter のほうは dep.notify してて、これは更新を伝えるためのものだろう。
Dep がわからんとわからんので src/observer/dep.jsを読む。
メンバ変数としてid, subsを持ってて、 target というクラス変数がある。
https://github.com/vuejs/vue/blob/1.0.4/src/observer/dep.js#L11L19
target には Watcher が入るみたいだがよくわからん。
さっきよく呼ばれてた depend は、target の addDep を呼び出すもの。
うーん
watcher も見る。
https://github.com/vuejs/vue/blob/1.0.4/src/watcher.js
変更があったら何かを実行するのがこいつの役目。
addDep は自分自身の依存( newDeps, deps )に渡された dep を追加するのと、 dep の sub に自分自身を追加している
ちょっとこんがらがってきた
各 Observer のインスタンスは dep を持っていて、値を get するときに Watcher の dep にインスタンスの dep を追加する
set の時には dep.notify を呼ぶことで、 dep の subs を更新して回る。
get の際に addDep したタイミングで 自分のdep.subs にその時点での Dep.target (=watcher) が追加されてる。
nofity で呼ばれる update は、設定によって同期/非同期で値を更新する。
https://github.com/vuejs/vue/blob/1.0.4/src/watcher.js#L209
実際に更新するのはこっちの run メソッド。 Batcher から実行するためのインターフェースだと書いてある
https://github.com/vuejs/vue/blob/1.0.4/src/watcher.js#L237
ここで watcher 自身の value を更新してコールバックを呼び出している。
watcher を使っている場所を見てみると、 src/directive.js などにある。
https://github.com/vuejs/vue/blob/1.0.4/src/directive.js#L117L129
ここで第3引数が callback なので this._update が Watcher の値が更新されるときに呼ばれる callback ということになる。
this._update は dir.update を呼んでると。
https://github.com/vuejs/vue/blob/1.0.4/src/directive.js#L103L107
update を代入しているのはここ。 descriptor を調べる必要がある。
https://github.com/vuejs/vue/blob/1.0.4/src/directive.js#L79L83
descriptor はコンストラクタで引数にとってるので Directive を初期化しているところを見に行く
https://github.com/vuejs/vue/blob/1.0.4/src/directive.js#L34
_bindDir で使われている
https://github.com/vuejs/vue/blob/1.0.4/src/instance/lifecycle.js#L109L113
_bindDir を使っているところはいくつかあるが、わかりやすそうなところでいくとここ
https://github.com/vuejs/vue/blob/1.0.4/src/compiler/compile.js#L395
で、 token.descriptor は何かなというと、雑に見ると恐らく下記
https://github.com/vuejs/vue/blob/1.0.4/src/compiler/compile.js#L360L365
publicDirectives[type] になる。
publicDirectives は何かというと
https://github.com/vuejs/vue/blob/1.0.4/src/compiler/compile.js#L2
var publicDirectives = require('../directives/public')
これなので、 directives/public を見てみる
https://github.com/vuejs/vue/tree/1.0.4/src/directives/public
各種ディレクティブのファイルが置いてある。
簡単そうだから text.js を見てみると、 update が定義されてる
https://github.com/vuejs/vue/blob/1.0.4/src/directives/public/text.js#L11L13
update: function (value) {
this.el[this.attr] = _.toString(value)
}
まさに要素の中身を書き換えているようだ
まだ全体像は見えてないが、なんとなく値が更新された時に DOM にも反映される流れはつかめた。
__ob__ の役割とかよくわかってないな。また後で使いつつ読んでみる。
しかし大変だなー。 Object.observe が使えれば楽なのに