(まとまりのなさがひどすぎてアレで放置してたらもう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
が使えれば楽なのに