作って分かるJavaScriptでデータバインド


## はじめに

JavaScriptにおけるデータバインドの実装方法を双方向データバインドライブラリのKnockoutJSのソースコードを読み、自分で以下のような最低限の機能を実装をしていきます。

* 変更を`subscribe`できる`observable`オブジェクト
* 他の`observable`オブジェクトの値の変更を検知して、自身の値を変える`dependentObservable`オブジェクト

上記の機能を実現するために、KnockoutJSの以下のオブジェクトの仕組みを解析しました。

* ko.subscribable
* ko.observable
* ko.dependencyDetection
* ko.dependentObservable(ko.computed)

この記事のソースコードはgithubで提供されています。

* https://github.com/tenntenn/js-binding-hundson

それぞれの機能をいくつかのステップに分けて説明します。各ステップは`step0`からディレクトリに分けられています。ステップ毎にスケルトンコードと説明(`README.md`)があります。スケルトンコードには`TODO`が書かれており、埋める事でコードを完成させることが出来ます。なお、各ステップの解答例は`answers`以下に同じディレクトリ構成で置かれています。

なお、この内容はKnockoutJSについて11月のALM（社内勉強会）で発表したものを基にしています。
勉強会のスライドはこちら。

* http://www.slideshare.net/takuyaueda967/knockout-js-alm11

## STEP 0: 前準備
まずは、前準備をしましょう。
このステップでは、特に`TODO`はありません。
他のステップで使う、いくつかの関数やオブジェクトを用意しましょう。

はじめに、グローバルオブジェクトを極力汚さないように、名前空間を用意しましょう。
名前空間の名前は特に意味はありませんが、KnockoutJSの`ko`をマネして、2文字くらいが使いやすいでしょう。
今回は、無作為に2文字選びました。

```javascript
var go = {};
```

つぎに、デバッグ用の`log`関数を用意しましょう。
`console.log`を使ってログをコンソールに出力し、ブラウザを使った場合には、JSON形式でHTMLのリストでも出力する関数です。

```javascript
var global = this;

// log
(function (selector) {
  function log() {
    console.log.apply(console, arguments);
    var args = [].slice.apply(arguments);
    var logStr = (args.length > 1)? JSON.stringify(args) : JSON.stringify(args[0]);
    if (selector && global.document) {
      var lst = document.querySelector(selector + '>' + 'ul');
      if (!lst) {
        lst = document.createElement('ul');
        document.querySelector(selector).appendChild(lst);
      }
      lst.innerHTML += '<li>'+logStr+'</li>';
    }
  }
  go.log = log;
})('#result');
```

さいごに、継承を行なうための関数を用意しましょう。
`go.extend`は第1引数で渡したオブジェクトのプロパティを第2引数で渡したオブジェクトにコピーする関数です。継承とはちょっと異なりますが、ここでは簡単の為にこうしておきましょう。この関数は深くコピーすることに注意しておきましょう。コピーしたプロパティのオブジェクト(配列)は別のオブジェクト(配列)となります。

```javascript
// extend
(function () {
  function extend(_super, sub) {
    for (var k in _super) {
      if (!_super.hasOwnProperty(k)) {
        continue;
      }

      if (Array.isArray(_super[k])) {
        sub[k] = _super[k].slice(0);
        for(var i = 0; i < _super[k].length; i++) {
          if (typeof _super[k][i] === 'object') {
            sub[k][i] = {};
            extend(_super[k][i], sub[k][i]);
          }
        }
      } else if (typeof _super[k] === 'object') {
        sub[k] = {};
        extend(_super[k], sub[k]);
      } else {
        sub[k] = _super[k];
      }
    }
  }
  go.extend = extend;
})();
```

## STEP 1: `subscribe`と`notify`

### バインディングの魔法

私はバインディングという機能が好きで、JavaFXやAngularJS、そしてKnockoutJSのバインディングを色々ためしました。また、面白そうだったので、自分で[バインディングライブラリ](https://github.com/tenntenn/simple-binding.js)を作ってみたりしました。

KnockoutJSのバインディングはHTMLとJSのオブジェクトを双方向バインドすることができます。双方向なので、片方が変更されるともう片方も変更されます。
たとえば、以下の例を見てみると、`input`タグの`value`に、`vm.count`がバインディングされています。`vm.count`の値は、タイマーでカウントアップされ、その結果が`input`の`value`へと反映されます。また、`input`の`value`をフォームから変更することもでき、その変更は`vm.count`へと反映されます。

[JSFiddleで見る](http://jsfiddle.net/uedatakuya/HXPb4)

```html
counter: <input type="number" id="counter" data-bind="value: count" />

```

```javascript
var vm = {
    count: ko.observable(0),
};

ko.applyBindings(vm, document.body);

setInterval(function() {
    vm.count(parseInt(vm.count(), 10) + 1);
}, 1000);
```

上記の例を見ると、KnockoutJSのバインディングは魔法のようにオブジェクトとHTMLをバインドしているようです。
もちろん、魔法などではありません。バインドされているもう一方に変更を伝える仕組みが働いていて、変更に追従しているのです。
この変更を伝える仕組みをKockoutJSでは、`ko.subcribable`が担っています。
KnockoutJSでメインに使われる`ko.observable`や`ko.observableArray`、`ko.computed`(`ko.dependentObservable`のエイリアス)はこのオブジェクトの機能を継承しています。

### `subscribe`と`notify`

KnockoutJSの`ko.subcribable`は変更を伝える役目を担っており、以下の2つの機能から成っています。

* `subscribe`：変更を通知してもらうオブジェクト(`subscripter`)を登録する
* `notifySubscripter`：登録している`subscripter`に通知する

KnockoutJSのソースコードを参考にしながら、`TODO`を埋めていきましょう。

* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/subscribable.js

```javascript
// subscribable
(function () {
    var subscribable = {
        _subscriptions: [],
        subscribe: function (callback, callbackTarget) {
            var _subscribable = this;
            var subscription = {
                callback: /* TODO: callbackTargetがあったらバインドしてコールバックを設定*/,
                dispose: function () {
                    // TODO: 破棄したというフラグを立てておく

                    // TODO: _subscribable._subscriptionsの中に自身(this)を発見したら配列から取り除く
                },
                isDisposed: false
            };
            this._subscriptions.push(subscription);
            return subscription;
        },
        notifySubscripters: function (valueToNotify) {
            var _subscriptions = this._subscriptions.slice(0);
            for (var i = 0; i < _subscriptions.length; i++) {
                var subscription = _subscriptions[i];
                // TODO: disposeされていなかったら、callback呼び出す
            }
        }
    };
    go.subscribable = subscribable;
})();
```

`subscription`は`callback`と`dispose`というプロパティを持っており、`callback`は引数で渡された`callback`に`callbackTarget`を`Function.bind`したものです。一方、`dispose`は`_subscriptoions`から自身を取り除く処理を行なう関数です。`_subscriptions`から取り除いただけでは、`subscription`オブジェクトから分からないので、フラグを立てておきます。`subscribe`は`subscription`を返すので、登録を破棄したければ、`dispose`を呼べば破棄されます。

つづいて、`notifySubscripters`を考えてみましょう。`notifySubscripters`は、`_subscriptions`に登録されている`subscription`の`callback`を呼び出す事によって、`subscript`しているオブジェクト(`subscripter`)に通知を送るメソッドです。`notifySubscripters`の引数は通知する値です。引数で渡された値は`subscription`の`callback`関数の引数として渡されます。

`TODO`をすべて埋めたらindex.htmlをブラウザで開いて、以下のテストプログラムの動作を確認してみましょう。

```javascript
// Test
(function () {

    // 継承してみる
    var subscribable = {};
    go.extend(go.subscribable, subscribable);

    // 通知がきたら、ログに出力する
    var subscription = subscribable.subscribe(function (value) {
        go.log(value);
    });

    // subscripterに'hoge'を通知する
    subscribable.notifySubscripters('hoge');

    // disposeして破棄する
    subscription.dispose();

    // subscripterに'piyo'を通知する
    // しかし、subscriptionはdisposeされているので、通知されない
    subscribable.notifySubscripters('piyo');
})();
```

以下のように出力されているはずです。

* "hoge"

コメントにも書かれていますが、1回目の`notifySubscripters`では、`subscription`の`callback`に通知されます。しかし、2回目は通知する`subscripter`がないので、どのオブジェクトにも通知されません。

## STEP 2: 値の更新を通知するオブジェクト

### `observable`オブジェクト

`subscribable`オブジェクトは、通知の登録(`subscribe`)と通知(`notify`)はできましたが、値そのものを保持することはできませんでした。
そこで、このステップでは内部に値を保持できる`observable`オブジェクトを作る関数を実装してみましょう。

KnockoutJSの`ko.observable`は、内部に値を保持できるオブジェクトを作る関数です。このオブジェクトは、関数オブジェクトで引数を渡して呼び出すと書き込み(write)になり、引数なしで呼び出すと読み出し(read)になります。`ko.observable`では、内部に保持する値の変更を`subscribe`することで、検知することができます。`subscribe`の機能は、`ko.subscribable`を継承することで実現しています。つまり、書き込み時に`notifySubscripters`を実行する事で、書き込みを通知します。具体的な使い方を見てみましょう。

```javascript
var foo = ko.observable(100);
foo.subscribe(function(v) {
    alert(v);
});
// 200とalertされる
foo(200);
// 200と出力される
console.log(foo());
```

上記の例では`var foo = ko.observable(100);`で初期化が行なわれ、`observable`オブジェクトが作られています。この時初期値として、`100`が設定されています。つづいて、`subcribable`で、変更を検知して、`alert`を呼び出すコールバック仕掛けます。`foo(200)`で書き込みが行なわれると、仕掛けたコールバックが呼ばれて、`alert`が行なわれます。`foo()`のように引数なしで呼ばれると、内部に保持している値をそのまま返すため、コンソールに`200`が出力されます。

それでは、KnockoutJSのソースコードを参考にしながら、`TODO`を埋めていきましょう。

* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/observable.js

```javascript
// observable
(function () {
  var observable = function (initialValue) {
    var lastValue = initialValue;

    function observable() {
      if (/* TODO: 引数があれば書き込み */) {
        // write
        var newValue = arguments[0];
        // TODO: 値が変わっていれば、更新して、nofityする
      } else {
        // read
        return lastValue;
      }
    }
    // TODO: go.subscribableを継承する
    // TODO: observableなオブジェクトを返す
  };
  go.observable = observable;
})();
```

`observable`オブジェクト（値が保持できて、変更の検知ができる）を作る関数を`go.observable`とします。`go.observable`の引数は、`observable`オブジェクトに設定する初期値を受取りましょう。戻り値はもちろん`observable`オブジェクトです。

`observable`という関数が2つ出てきてややこしいですが、外側の関数(`go.observable`)は`ko.observable`にあたる関数で、内側の関数が実際に値を保持する`observable`なオブジェクトになります。`go.observable`が呼び出される度に、内側の`observable`関数(`observable`オブジェクト)が作られます。`lastValue`は`observable`オブジェクト内に保持する値を入れる変数で、`go.observable`の引数で貰った値を初期値としています。通知する機能は`go.subscribable`で作っているため、`observable`オブジェクトは、`go.subscribable`を継承しています。

`observable`オブジェクトは2種類の呼ばれ方をしています。ひとつめが`foo(200)`のような引数がある呼ばれ方、ふたつめが`foo()`のような引数がない呼ばれ方です。前者が書き込みで後者が読み出しです。JavaScriptの関数の中では、`arguments`という引数を保持する配列っぽい特別なオブジェクトが使えます。`arguments`は、複数のパターンの引数をとる場合によく使われます。この場合は、`arguments.length`を見て、引数の有無で書き込み処理と読込み処理を分ければ良いでしょう。読み込み処理の場合は、単に最後に設定された値(`lastValue`)を返すだけでいいでしょう。書き込み処理も、第1引数の値を`lastValue`に入れれば良さそうです。書き込み処理では、もうひとつ大切な処理を行なう必要があります。新しい値が設定された場合、その事を`subscripter`に通知する処理です。これは`go.subscribable`から継承した`notifySubscripters`を使えば済みます。

`TODO`をすべて埋めたらindex.htmlをブラウザで開いて、以下のテストプログラムの動作を確認してみましょう。

```javascript
// Test
(function () {
    var observable = go.observable('hoge');
    var count = 0;
    var subscription = observable.subscribe(function () {
        count++;
        go.log(observable(), count);
    });
    observable('foo');
    observable('foo');
    observable('bar');
    subscription.dispose();
    observable('piyo');
})();
```

以下のように出力されているはずです。

* ["foo",1]
* ["bar",2]

値が新しい値に変更された場合のみ、通知が行なわれていることが分かります。

## STEP 3: 依存関係の検出と`dependencyDetection`

### 依存関係の検出

KnockoutJSには、`ko.computed`という他の`ko.observable`の値に依存したような`observable`なオブジェクトがあります。`ko.computed`は以下のように使います。

[JSFiddleで見る](http://jsfiddle.net/uedatakuya/52J5b)

```html
<div id="result"></div>
```

```javascript
var hoge = ko.observable(100);
var piyo = ko.observable(200);
var foo = ko.computed(function(){
    return hoge() + piyo();
});
foo.subscribe(function(v) {
    document.querySelector('#result').innerText = v;
});
hoge(300);
```

`foo`の値(引数なしで評価された値)は、`hoge`が`piyo`が変更された場合、自動的に更新されます。もちろん、`subscribe`しておけば、その変更は通知されます。
不思議ですよね？KnockoutJSはどのようにして、`foo`が`hoge`と`piyo`に依存していることを知っているのでしょう？

この不思議を解明するために、以下のように、`ko.computed`に渡す関数が評価される回数を数えてみましょう。

[JSFiddleで見る](http://jsfiddle.net/uedatakuya/MckQ8)

```html
<div id="result"></div>
```

```javascript
var hoge = ko.observable(100);
var piyo = ko.observable(200);
var count = 0;
var foo = ko.computed(function () {
    count++;
    document.querySelector('#result').innerText = count;
    return hoge() + piyo();
});
hoge(300);
```

直感的には、`hoge(300)`の変更の時に1度呼ばれているだけの様な気がしますが、結果は`2`となっています。何故でしょう？
実は、この関数は`ko.computed`に渡されたときに、1度評価されます。そして、その評価の時に評価された`observable`オブジェクト(ここでは、`hoge`と`piyo`)を記録することで、依存関係を検知しています。また、その時の評価値を初期値として設定しています。

これはKnockoutJSを使う上では非常に大切なことです。なぜならば、この`ko.computed`に渡す関数内で副作用のある処理をすると、意図しない結果になる場合があるからです。事前に呼び出されることをちゃんと知った上で、使わないと思わぬバグになってしまいます。

### `dependencyDetection`オブジェクト

KnockoutJSの`ko.dependencyDetection`は、前述の依存関係の検出を行なうオブジェクトです。`ko.dependencyDetection.begin`を実行した後に、評価された`obsrvable`オブジェクトを登録します。`dependencyDetection`オブジェクトには、依存関係の検出を始める`begin`メソッドと、依存関係に`subscribable`オブジェクトを追加する`registerDependency`メソッドが必要です。また、`go.observable`の方にも修正が必要です。読込みの場合に、依存関係を記録する必要があるため、引数なしで呼び出された場合の処理に追加しなければなりません。

それでは、KnockoutJSのソースコードを参考にしながら、`TODO`を埋めていきましょう。

* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/dependencyDetection.js

```javascript
// observable
(function () {
    var observable = function (initialValue) {
        var lastValue = initialValue;

        function observable() {
            if (arguments.length > 0) {
                // write
                var newValue = arguments[0];
                if (newValue !== lastValue) {
                    lastValue = newValue;
                    observable.notifySubscripters(lastValue);
                }
            } else {
                // read
                // TODO: 依存関係に追加(go.dependencyDetectionを使う)
                return lastValue;
            }
        }
        go.extend(go.subscribable, observable);
        return observable;
    };
    go.observable = observable;
})();

// dependencyDetection
(function () {
    var distinctDependencies, callback;
    var dependencyDetection = {
        begin: function (_callback) {
            // TODO: callbackを初期化
            // TODO: distinctDependenciesを初期化
        },
        registerDependency: function (subscribable) {
            // TODO: すでに依存関係にあった場合と1度もbeginが呼ばれていない場合は無視

            // TODO: subscribableを依存関係に追加
            // TODO: コールバックが設定してあったら呼び出す
        }
    };
    go.dependencyDetection = dependencyDetection;
})();
```

`TODO`をすべて埋めたらindex.htmlをブラウザで開いて、以下のテストプログラムの動作を確認してみましょう。

```javascript
// Test
(function () {
    var a = go.observable(100);
    var b = go.observable(200);
    var observables = [];
    go.dependencyDetection.begin(function(observable) {
        if (a === observable) {
            go.log('a');
        } else if (b === observable) {
            go.log('b');
        } else {
            go.log(observable);
        }
    });
    a();
    a();
    b();
})();
```

以下のように出力されているはずです。

* "a"
* "b"

`observable`オブジェクトの値が読み込まれた時に、`go.dependencyDetection.registerDependency`が呼び出されているのが分かります。また、1度登録された`observable`オブジェクトは重複して登録されていません。これで十分に動くように見えますが、実はうまく動きません。しかし、`dependentObservable`(`computed`)を実装する際に、必要となってくるので、ここはこのまま進みましょう。

## STEP 4: 計算結果の変更を検出する

### `dependentObservable`オブジェクト

前述の通り、KnockoutJSには`ko.computed`関数を使うと他の`observable`オブジェクトを使って計算した結果を自身の値とする`dependentObservable`オブジェクトを作ることができます。また、`dependentObservable`オブジェクトが他の`dependentObservable`に依存しても問題ありません。もちろん、通常の`observable`オブジェクトと同様に、`subscribe`できる必要があります。そのため、以下のように読込み処理の時に毎回値を評価する方法では、効率が悪く、`subscribe`しても依存する`observable`オブジェクトの値が更新されても、次の読込み処理が走るまで`notify`することができません。

```javascript
// dependentObservable
(function () {
    var lastValue;
    var dependentObservable = function (readFunction) {
        function dependentObservable() {
            if (arguments.length > 0) {
                // write
                throw new Error('Cannot write to computed');
            } else {
                // read                                
                var newValue = readFunction();
                if (newValue !== lastValue) {
                    lastValue = newValue;
                    dependentObservable.notifySubscripters(lastValue);
                }
                go.dependencyDetection.registerDependency(dependentObservable);
                return lastValue;
            }
        }

        go.extend(go.subscribable, dependentObservable);
        return dependentObservable;
    };
    go.dependentObservable = dependentObservable;
    go.computed = dependentObservable;
})();
```

依存関係を検出するには、STEP 3で作成した`go.dependencyDetection`を使います。検出された依存する`observable`オブジェクトを`subscribe`して変更された場合に`readFunction`を読み込み`lastValue`を更新します。

それでは、KnockoutJSのソースコードを参考にしながら、`TODO`を埋めていきましょう。

* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/dependentObservable.js

```javascript
// dependentObservable
(function () {
    var dependentObservable = function (readFunction) {
        var lastValue = null;
        var isBeingEvaluated = false;
        var hasBeenEvaluated = false;
        var subscriptionsToDependencies = [];

        function evaluateImmediate() {
            // TODO: evaluateImmediateが既に呼び出し済み(呼び出し済みフラグがtrue)なら何もしない

            // TODO: 呼び出し済みフラグをtrueにする

            // TODO: 記録しているsubscriptionsをdisposeして配列を空にする
            
            go.dependencyDetection.begin(function (subscribable) {
                // TODO: 検知されたsubscribableをsubscribeして、そのsubscriptionを記録
            });

            var newValue = readFunction();
            // TODO: 1度でも評価されているかどうかを表すフラグをtrueにする

            if (newValue !== lastValue) {
                lastValue = newValue;
                dependentObservable.notifySubscripters(lastValue);
            }

            // TODO: 呼び出し済みフラグをfalseにする
        }

        function dependentObservable() {
            if (arguments.length > 0) {
                // write
                throw new Error('Cannot write to computed');
            } else {
                // read                
                if (/* TODO: 一度も評価されていない場合 */) {
                    evaluateImmediate();
                }
                go.dependencyDetection.registerDependency(dependentObservable);
                return lastValue;
            }
        }

        go.extend(go.subscribable, dependentObservable);
        // TODO: 最初の評価と初期値の設定
        return dependentObservable;
    };
    go.dependentObservable = dependentObservable;
    go.computed = dependentObservable;
})();
```

`evaluateImmediate`は`readFunction`を呼び出し、保持している`observable`の値を更新します。また、その際に`go.dependencyDetection`を使って、依存する`subscribable`(`observable`)を検知します。検知した`subscribable`の`subscribe`する事で、更新された時に再度`evaluateImmediate`を呼び出されるようにします。`evaluateImmediate`が呼び出される度に、検知した依存関係を破棄し、`subscription`も破棄(`dispose`)するために、配列`subscriptionsToDependencies`で保持しています。`dependentObservable`オブジェクトも`observable`オブジェクトの一種なので、値が変更された場合は`notifySubscripters`で変更を通知します。`hasBeenEvaluated`は、一番最初の場合は問答無用で`evaluateImmediate`を呼び出すために使われます。

`TODO`をすべて埋めたらindex.htmlをブラウザで開いて、以下のテストプログラムの動作を確認してみましょう。

```javascript
// Test
(function () {
    var a = go.observable(100);
    var b = go.observable(200);
    var c = go.computed(function () {
        return a() + b();
    });
    go.log(a(), b(), c());
    a(400);
    go.log(a(), b(), c());
})();
```

以下のように出力されているはずです。

* [100,200,300]
* [400,200,600]

うまく動いているように見えます。しかし、これではうまく動かない場合があります。
次のステップでその部分を改良しましょう。

## STEP 5: 問題点の改善

このステップでは、STEP 4での実装をうまく動かない例を示して、問題点を改善していきます。

### 入れ子にした場合

STEP 4までの実装では、以下のように`computed`が入れ子になっている場合にうまく動作しません。最初の出力では、`100 200`、2番目の出力では`150 300`と出力されて欲しいところですが、実際には2番目の出力は`150 200`となります。STEP 4までの実装では、`readFunction`の中で`go.dependenctyDetection.begin`を多重に呼び出すことを前提としていません。そのため、2度`begin`を呼び出すと前回の呼び出しの分の`distinctDependencies`が消えてしまいます。そこで、`distinctDependencies`をスタックで管理するようにしましょう。スタックで管理すれば、入れ子に呼び出されても問題ありません。

```javascript
    var a = go.observable(100);

    var b = go.computed(function () {
        var c = go.computed(function () {
            return a() * 2;
        });
        return c();
    });

    go.log(a(),b());
    a(150);
    go.log(a(),b());

```

それでは、KnockoutJSのソースコードを参考にしながら、`TODO`を埋めていきましょう。

* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/dependencyDetection.js
* https://github.com/knockout/knockout/blob/v3.0.0/src/subscribables/dependentObservable.js

```javascript
// dependencyDetection
(function () {
  var frames = [];
  var dependencyDetection = {
    begin: function (callback) {
        // TODO: 新しいframeをpushして、依存関係の検出を開始する。callbackが無かったら、undefinedをpushする
    },
    end: function () {
        // TODO: 先頭のframeをpopして、依存関係の検出を終了する
    },
    registerDependency: function (subscribable) {
      if (frames.length > 0) {
        var topFrame = frames[frames.length - 1];
        // TODO: topFrameがundefinedの場合とすでにsubscribableが登録してあったら何もしない

        topFrame.distinctDependencies.push(subscribable);
        topFrame.callback(subscribable);
      }
    }
  };
  go.dependencyDetection = dependencyDetection;
})();

// dependentObservable
(function () {
  var dependentObservable = function (readFunction) {
    var lastValue = null;
    var isBeingEvaluated = false;
    var hasBeenEvaluated = false;
    var subscriptionsToDependencies = [];

    function evaluateImmediate() {
      if (isBeingEvaluated) {
        return;
      }
      isBeingEvaluated = true;
      for (var i = 0; i < subscriptionsToDependencies.length; i++) {
        subscriptionsToDependencies[i].dispose();
      }
      subscriptionsToDependencies = [];
      go.dependencyDetection.begin(function (subscribable) {
        subscriptionsToDependencies.push(subscribable.subscribe(evaluateImmediate));
      });
      var newValue = readFunction();
      hasBeenEvaluated = true;
      if (newValue !== lastValue) {
        lastValue = newValue;
        dependentObservable.notifySubscripters(lastValue);
      }
      // TODO: 依存関係の検出を終了する
      isBeingEvaluated = false;
    }

    function dependentObservable() {
      if (arguments.length > 0) {
        // write
        throw new Error('Cannot write to computed');
      } else {
        // read                
        if (!hasBeenEvaluated) {
          evaluateImmediate();
        }
        go.dependencyDetection.registerDependency(dependentObservable);
        return lastValue;
      }
    }

    go.extend(go.subscribable, dependentObservable);
    evaluateImmediate();
    return dependentObservable;
  };
  go.dependentObservable = dependentObservable;
  go.computed = dependentObservable;
})();
```

スタックを`frames`という名前にし、`begin`でスタックに積み、`end`でスタックから取り除くようにしています。また、`dependentObservable`の`evaluateImmediate`も循環で呼び出されないようにしましょう。`begin`したら`end`するのを忘れないようにしましょう。

`TODO`をすべて埋めたらindex.htmlをブラウザで開いて、以下のテストプログラムの動作を確認してみましょう。

```javascript
// Test
(function () {
    var a = go.observable(100);

    // 入れ子にしても問題ないか？
    var b = go.computed(function () {
        var c = go.computed(function () {
            return a() * 2;
        });
        return c();
    });

    go.log(a(),b());
    a(150);
    go.log(a(),b());
})();
```

以下のように出力されているはずです。

* [100,200]
* [150,300]

`b`の依存関係と`b`の`readFunction`の中で作られている`c`の依存関係はスタックを使うことで別物として扱うことができるようになりました。`a`を変更すると、`b`と`c`に変更が通知され、それぞれの`evaluateImmediate`が実行されます。もちろん、`c`は`b`の`readFunction`が呼ばれる度に生成されるので、普通はこのような呼び方はしないでしょう。

## まとめ

KnockoutJSの以下のオブジェクトの簡易実装を作ってみました。KnockoutJSのデータバインドは`dependenctyDetection`がキモで、この機構があるおかげで、明示的に依存関係を定義する必要がありません。しかしながら、その仕組みをあまり理解せずに使用していると、思わぬバグに遭遇して困るのではないでしょうか。ここでは、説明が複雑になるので、`ko.applyBindings`を使ったHTMLとの双方向バインディングの仕組みについて触れませんでしたが、ソースを軽く見た感じでは`computed`をうまく使って実装されているようです。また時間があれば、そちらの実装の方もまとめたいと思います。これを書いている間もmasterブランチのソースコードが結構変わっているので、内容が古くなるかもしれませんが、このまとめが誰かの役に立てば幸いです。