dependentObservableの仕組み

Knockout.jsを触り始めて最初に感じた疑問は、dependentObservableって何で依存先の変更を自動で感知できるのか?でした。

コードを読んでわかってきた自分なりの理解をまとめます。

例として、簡単な足し算アプリを使います。

コードはこちらにも貼り付けておきます。

HTML
<input data-bind="value: arg1" /> +
<input data-bind="value: arg2" /> =
<span data-bind="text: result">
JavaScript
var ViewModel = function() {
    this.arg1 = ko.observable(0);
    this.arg2 = ko.observable(0);
    this.result = ko.dependentObservable(function() {
        var arg1 = parseInt(this.arg1(), 10);
        var arg2 = parseInt(this.arg2(), 10);
        return arg1 + arg2;
    }, this);
};

ko.applyBindings(new ViewModel());

ViewModelを見てもらうとわかりますが、resultプロパティがdependentObservableです。dependentというからには何かに依存しているのですが、何に依存しているかというとarg1とarg2のプロパティです。
でも依存元のresultと依存先のarg1やarg2を紐つける明示的なステートメント(arg1.subscribe(result);みたいな)はどこにもありません。でも、arg1やarg2に対応するTextBoxの値が変更されると、それに連動してresultに対応するspanタグの値も変わります。これはいったいどういう仕組みだろう?と思ったわけです。

結論から言えば、dependentObservableの第一引数に渡した関数(以降Xと呼びます)が最初に実行するタイミングで紐つけ処理が行われています。どういうことかというと、Xが実行中の間にobservableからデータを読み取る処理(this.arg1()など)が行われたら、Xをobservableにsubscribeします。で、一度subscribeした後は、observableにデータを書き込む処理(this.arg1(100)など)が行われたら変更をXにnotifyするようになります。Xそのものがsubscribeされるわけではないのですが、概略としてはそんな感じです。
ちょっと乱暴に言ってしまうと、ko.observableやko.dependentObservableは、値のRead/Writer処理をフックしていろいろやってくれるということです。その結果、明示的に書くと繰り返しが多くなってしまいがちな処理が不要になるんですね。

ところで、Xの中で依存先のobservableな値を書き換えたら、呼び出しの循環が起きそうですが、その辺はちゃんとチェックしていて、そのようなことをしてもXは一度しか呼び出されないようになっていました。

ちなみにですが、今回は、observableとdependentObservableにおける値の変更の通知に注目しました。observableやdependentObservableの値とDOMの要素における値の変更通知はまた別の仕組みで行われています(バインディングと呼ばれている)。