Memory leak with observableArray and foreach binding

1.1k Views Asked by At

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:

  • Using Chrome, open the dev tools panel and select 'Profiles'> 'Record Heap Allocations'
  • Click once to fetch some data
  • Start the profiler and take a heap snapshot
  • Click lots of times
  • Take another snapshot and compare against the first

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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!

0
On

I was messing with your fiddle and noticed that if you push vanilla objects with observable properties instead of pushing the vanilla object wrapped in an observable, the problem goes away:

    joes.forEach(function(joe){
            var joeObs = {
                name:ko.observable(joe.Name),
                age:ko.observable(joe.Age)};
            self.array.push(joeObs);
        });

I have no explanation for this. So my answer is more of a question... what's the difference really? Why does one leak and the other not?