我应该如何清理KnockoutJS ViewModels?

And*_*ker 11 javascript knockout.js

我有一个单页面的应用程序,用户可以在其中浏览项目列表.反过来,每个项目都有一个项目列表.

使用通过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>
Run Code Online (Sandbox Code Playgroud)

JavaScript/KnockoutJS ViewModels:

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);
Run Code Online (Sandbox Code Playgroud)

如果你看一下这个小提琴,你会发现"log"包含了每个创建过的ViewModel的条目.SubItem.computed即使在我预期每个项目早已消失之后,计算属性仍会运行.这导致我的应用程序性能严重下降.

所以我的问题是:

  • 我在这做错了什么?我是否期望KnockoutJS处理我实际需要手动处理的ViewModel?
  • 我使用的ko.computedSubItem导致问题吗?
  • 如果KnockoutJS不打算处理这些视图模型,我应该如何处理它们?

更新:进一步挖掘后,我很确定计算属性SubItem是罪魁祸首.但是,我仍然不明白为什么仍在评估该属性.SubItem更新可观察数组时不应该销毁?

Mic*_*est 8

只要删除了对它及其依赖项的所有引用,JavaScript垃圾收集器就只能处理一个计算的observable.这是因为observables保留了对依赖于它们的任何计算的observable的引用(反之亦然).

一种解决方案是使计算的observable在不再具有任何依赖性时自行配置.这可以使用像这样的辅助函数轻松完成.

function autoDisposeComputed(readFunc) {
    var computed = ko.computed({
        read: readFunc,
        deferEvaluation: true,
        disposeWhen: function() {
            return !computed.getSubscriptionsCount();
        }
    });
    return computed;
}
Run Code Online (Sandbox Code Playgroud)