Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Hunting Memory Leaks in Backbone

Ever notice your backbone app slows down considerably after using it for a few minutes? Or maybe your event callbacks are called more than once each time the event fires? Or my favorite, Chrome runs out of memory if you have a few tabs open that have run your entire Jasmine suite? This means you have backbone leaks.

I just spent the last week hunting these down in a very large app. It turns out the problem is tractable with a few guidelines. None of this is ground-breaking, but I wanted to put it all in one place.

1) The object retaining tree in the Chrome heap profiler is amazing. Use it.

2) Restrict use of views in closures, or as contexts to callbacks in event bindings. This is explained in much more detail here. I’ve also personally found nothing but trouble with _.bind for similar reasons.

More specifically, never use model.bind or model.on in a view without keeping track of that binding and remembering to unbind it when you want to tear down the view. This is the motivation for coccyx

3) ParentViews with childViews need to keep track of their children. When you tear down the parent view, you also tear down the child views. Before implementing this step, the object retaining tree for my zombie views was shockingly large. Afterwards, it was a manageable ~4 retainers for the zombies.

Parents keeping track of their children seems simple enough, but it is easy to lose track of the little devils.

For example:

ParentView = Backbone.View.extend({
    render: {
        //render the rest of the view first

        this.collection.each(function(model){
            var childView = new ChildView({model: model});
            this.$("ul").append(childView.render().$el);
        });
    }
});

loses any reference to the child views, and they will almost certainly leak (assuming the child views bind events to their models that need to be cleaned up).

Using the principles names from coccyx, the same code needed to be rewritten as

ParentView = Backbone.View.extend({
    render: {
        //render the rest of the view first

        this.tearDownRegisteredSubViews();

        this.collection.each(function(model){
            var childView = new ChildView({model: model});
            this.$("ul").append(liView.render().$el);

            this.registerSubView(childView);

        });
    }
});

where tearDownRegisteredSubviews calls tearDown on any registered subviews. This way, the only child views still in memory are those initialized in the most recent call to render. Those remaining views should be torn down when the parent is torn down.

View.tearDown itself can be a bit sticky because you still have to worry about event bindings from global objects and other difficult-to-clean sources, but those solutions are more project-specific.

Hope that points you in the right direction.

Comments
  1. Nice bullet points. I think these are good rules to follow.

    I wrote a post on using Chrome Developer tools to hunt down those memory leaks and “Detached DOM trees” that readers might also find helpful: http://andrewhenderson.me/tutorial/how-to-detect-backbone-memory-leaks/

    Thanks for writing!

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *