У меня есть одностраничное приложение, в котором пользовательские страницы просматривают списки элементов. Каждый элемент, в свою очередь, имеет список элементов.
Наблюдаемый массив обновляется новыми элементами с сервера, полученного через запрос 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 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);
Если вы посмотрите на скрипту, вы заметите, что "журнал" содержит запись для каждого созданного ViewModel. вычисленное свойство SubItem.computed
выполняется даже после того, как я ожидал, что каждый из этих элементов будет удален. Это вызывает серьезное ухудшение производительности в моем приложении.
Итак, мои вопросы:
- Что я здесь делаю неправильно? Я ожидаю, что KnockoutJS избавится от ViewModels, который мне действительно нужно утилизировать вручную?
- Является ли использование
ko.computed
наSubItem
моей проблемой? - Если KnockoutJS не собирается удалять эти режимы просмотра, как я должен сам избавляться от них?
Обновление: После некоторого дальнейшего копания, я уверен, что вычисленное свойство в SubItem
является виновником. Тем не менее, я все еще не понимаю, почему это свойство все еще оценивается. Не следует ли разрушать SubItem
при обновлении наблюдаемого массива?