Search code examples
javascriptknockout.jsmemory-leaksko.observablearray

Memory leak with observableArray and foreach binding


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?


Solution

  • 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!