11

ユーザーがアイテムのリストをページングする単一ページのアプリがあります。各アイテムには、アイテムのリストがあります。

監視可能な配列は、AJAX 要求を介して取得されたサーバーからの新しいアイテムで更新されます。これはすべてうまくいきます。

残念なことに、数ページ後、実行される操作の数 (および FireFox や IE8 などのブラウザーで使用されるメモリの量) は増え続けます。監視可能な配列内の項目を新しいデータに置き換えたにもかかわらず、監視可能な配列内の要素が適切にクリーンアップされておらず、実際にはまだメモリ内にあるという事実まで追跡しました。

私が見ている問題を再現する小さな例を作成しました:

HTML:

<p data-bind="text: timesComputed"></p>
<button data-bind="click: more">MORE</button>
<ul data-bind="template: { name: 'items-template', foreach: items }">
</ul>

<script id="items-template">
    <li>
        <p data-bind="text: text"></p>
        <ul data-bind="template: { name: 'subitems-template', foreach: subItems }"></ul>
    </li>
</script>

<script id="subitems-template">
    <li>
        <p data-bind="text: text"></p>
    </li>
</script>

JavaScript/KnockoutJS ViewModel:

var subItemIndex = 0;

$("#clear").on("click", function () {
  $("#log").empty();
});

function log(msg) {
  $("#log").text(function (_, current) {
    return current + "\n" + msg;
  });
}
function Item(num, root) {
  var idx = 0;

  this.text = ko.observable("Item " + num);
  this.subItems = ko.observableArray([]);
  this.addSubItem = function () {
    this.subItems.push(new SubItem(++subItemIndex, root));
  }.bind(this);

  this.addSubItem();
  this.addSubItem();
  this.addSubItem();
}

function SubItem(num, root) {
  this.text = ko.observable("SubItem " + num);
  this.computed = ko.computed(function () {
    log("computing for " + this.text());
    return root.text();
  }, this);

  this.computed.subscribe(function () {
    root.timesComputed(root.timesComputed() + 1);
  }, this);
}

function Root() {
  var i = 0;

  this.items = ko.observableArray([]);
  this.addItem = function () {
    this.items.push(new Item(++i, this));
  }.bind(this);

  this.text = ko.observable("More clicked: ");
  this.timesComputed = ko.observable(0);

  this.more = function () {
    this.items.removeAll();
    this.addItem();
    this.addItem();
    this.addItem();    
    this.timesComputed(0);
    this.text("More clicked " + i);
  }.bind(this);

  this.more();
}

var vm = new Root();

ko.applyBindings(vm);

フィドルを見ると、「ログ」には、これまでに作成されたすべての ViewModelのエントリが含まれていることがわかります。計算されたプロパティSubItem.computedは、これらの各アイテムがなくなったと予想した後でも実行されます。これにより、アプリケーションのパフォーマンスが大幅に低下しています。

だから私の質問は

  • ここで何が間違っていますか?実際に手動で破棄する必要がある ViewModel を KnockoutJS が破棄することを期待していますか?
  • ko.computedonの使用がSubItem問題の原因ですか?
  • KnockoutJS がこれらのビューモデルを破棄しない場合、どのように自分で破棄する必要がありますか?

更新:SubItemさらに掘り下げた後、計算されたプロパティが原因であると確信しています。ただし、そのプロパティがまだ評価されている理由はまだわかりません。SubItem観測可能な配列が更新されたときに破棄されるべきではありませんか?

4

1 に答える 1

8

JavaScriptガベージコレクターは、JavaScriptガベージコレクターへのすべての参照とその依存関係が削除された場合にのみ、計算されたオブザーバブルを破棄できます。これは、オブザーバブルが、それらに依存する計算されたオブザーバブルへの参照を保持しているためです(その逆も同様です)。

1つの解決策は、依存関係がなくなったときに、計算されたオブザーバブルがそれ自体を破棄するようにすることです。これは、このようなヘルパー関数を使用して簡単に実行できます。

function autoDisposeComputed(readFunc) {
    var computed = ko.computed({
        read: readFunc,
        deferEvaluation: true,
        disposeWhen: function() {
            return !computed.getSubscriptionsCount();
        }
    });
    return computed;
}
于 2013-01-18T00:40:04.770 に答える