How to avoid a memory leak when instantiating child views in Backbone.js

myView =  Backbone.View.extend({
    //event binding etc etc

    render: function() {
        //render some DOM
    }
})

anotherView = Backbone.View.extend({
    events: {
        'click .selector doThis'
    },

    createNewView: function() {
        var view = new myView();
    }
})

createNewView may be called multiple times. My understanding is that the variable view will not necessarily be removed by JavaScript's built-in garbage collection because it references objects/code which still exists when the createNewView function completes.

Is this correct? How to deal with this?

My current approach is to initialise myView once at the level of my app:

myApp.view = new myView()

Then in createNewView I just call render on this:

myApp.view.render()

Essentially, I only ever have one of them and I re-use it.

An alternative approach is to track the creation of sub views in an array and then I call .remove() on each one in turn when I know they are no longer needed.

Am I on the right track?

It occurs to me that the second approach is better because if myView created bound callbacks on other objects with listenTo, these would not be removed simply by re-assigning the variable. That is, if I am calling new to instantiate a new instance of the view, I should call remove() on the being discarded instance first... It seems.

Answers:

Answer

In your example, you don't put the view's el into the DOM, so nothing is referencing the view, then it will be collected by the garbage collection.

One good thing to ensure a view isn't bound to something anymore is to call .remove() on it. It will remove:

  • the view's el from the DOM,
  • the jQuery DOM events
  • the Backbone event listeners.

The Backbone .remove source:

// Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners.
remove: function() {
    this._removeElement();
    this.stopListening();
    return this;
},

// Remove this view's element from the document and all event listeners
// attached to it. Exposed for subclasses using an alternative DOM
// manipulation API.
_removeElement: function() {
    this.$el.remove();
},

As mentioned by mu is too short in the comments (and myself in almost every other answers), you should always favor listenTo over on or bind to avoid memory leaks and ease unbinding events.

When rendering child views, nested inside a parent view, a good practice is to keep an array of the child views to later call .remove() on each of them.

A simple list view might look like this:

var ListView = Backbone.View.extend({
    initialize: function() {
        // Make an array available to keep all the child views
        this.childViews = [];
    },
    addOne: function(model) {
        var view = new Backbone.View({ model: model });

        // as you create new views, keep a reference into the array.
        this.childViews.push(view);

        this.$el.append(view.render().el);
    },

    renderList: function() {
        // replace the view content completely with the template
        this.$el.html(this.templates());

        // then cleanup
        this.cleanup();

        // then render child views
        this.collection.each(this.addOne, this);

        return this;
    },

    cleanup: function() {
        // quick way to call remove on all views of an array
        _.invoke(this.childViews, 'remove');
        // empty the array
        this.childViews = [];
    },
});

Though if other objects are listening to it, it won't be collected and may be a leak. It's important to keep track of the references and delete them all when you don't need it anymore.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.