We have a SPA that fetches small batches of items via AJAX and uses them to populate a knockout observableArray that is bound to the DOM via foreach.
When new data arrives we clear the old array using removeAll() and push the new items in. Using the Chrome profiling tools we've found that this causes a memory leak with a load of arrays and closures left dangling. The more fetches, the bigger the leak.
We've built a simple test case that demonstrates the problem (see this fiddle). To reproduce:
Html:
<div data-bind="click:go,
text:(clickCount()==0)
? 'click once then take a heap snapshot'
: 'click me lots then take another heap snapshot to compare'"
style="cursor:pointer"></div>
<ul data-bind="foreach:array">
<div data-bind="text:$data.name"></div>
<div data-bind="text:$data.age"></div>
</ul>
Javascript:
var getJoes = function(){
var joes=[];
for(var i=0;i<10;i++)
{
var name="Joe";
var age=((Math.random()*10)+1)>>0;
joes.push({Name:name,Age:age});
}
return joes;
};
function viewModel(){
var self=this;
self.array = ko.observableArray();
self.clickCount=ko.observable(0);
self.go = function(){
self.clickCount(self.clickCount()+1);
self.array.removeAll();
var joes=getJoes();
joes.forEach(function(joe){
var joeObs = ko.observable({
name:ko.observable(joe.Name),
age:ko.observable(joe.Age)});
self.array.push(joeObs);
});
};
}
ko.applyBindings(new viewModel());
Is this a bug or are we missing something?
We took the test case out of jsfiddle, ran it stand-alone and bingo - no memory leak. Phew.
Seems like it gets quite a bit more involved in the dance than expected!