エンジニアですよ!

頑張れ俺くん、巨匠と呼ばれるその日まで

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] という関数(?)になっちゃってる。


Observerhttps://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 のラッパー

対象が配列なら何とかして(protoAugmentcopyAugment)配列用の便利関数的なものを生やしまくる。

そして walk で実際に convert していく様子

convertdefineReactive を呼び出す。

defineReactive によって、オブジェクトに getter/setter が定義されてる。

getter のほうは、値を返す前に depdepend をたくさん呼んでるが、これはなんだろう。

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 は、targetaddDep を呼び出すもの。

うーん

watcher も見る。

https://github.com/vuejs/vue/blob/1.0.4/src/watcher.js

変更があったら何かを実行するのがこいつの役目。

addDep は自分自身の依存( newDeps, deps )に渡された dep を追加するのと、 depsub に自分自身を追加している

ちょっとこんがらがってきた

Observerインスタンスdep を持っていて、値を get するときに Watcherdepインスタンスdep を追加する

set の時には dep.notify を呼ぶことで、 depsubs を更新して回る。

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._updatedir.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 が使えれば楽なのに