I always nuke the views and sometimes reuse the models. Making sure the views are deallocated can be a pain, if you keep the models around. The models may keep a reference to the view if they are not unbound properly.
As of Backbone ~0.9.9, binding models with view.listenTo() rather than model.on() allows for easier cleanup through inversion of control (views control bindings as opposed to models). If view.listenTo() is used to bind, then a call to view.stopListening() or view.remove() will remove all bindings. Similar to calling model.off(null, null, this).
I like to cleanup views by extending the view with a close function that calls sub-views semi-automatically. The subviews must be referenced by properties of the parent or they must be added to an array within the parent called childViews[].
Here is the close function that I use..
// fired by the router, signals the destruct event within top view and
// recursively collapses all the sub-views that are stored as properties
Backbone.View.prototype.close = function () {
// calls views closing event handler first, if implemented (optional)
if (this.closing) {
this.closing(); // this for custom cleanup purposes
}
// first loop through childViews[] if defined, in collection views
// populate an array property i.e. this.childViews[] = new ControlViews()
if (this.childViews) {
_.each(this.childViews, function (child) {
child.close();
});
}
// close all child views that are referenced by property, in model views
// add a property for reference i.e. this.toolbar = new ToolbarView();
for (var prop in this) {
if (this[prop] instanceof Backbone.View) {
this[prop].close();
}
}
this.unbind();
this.remove();
// available in Backbone 0.9.9 + when using view.listenTo,
// removes model and collection bindings
// this.stopListening(); // its automatically called by remove()
// remove any model bindings to this view
// (pre Backbone 0.9.9 or if using model.on to bind events)
// if (this.model) {
// this.model.off(null, null, this);
// }
// remove and collection bindings to this view
// (pre Backbone 0.9.9 or if using collection.on to bind events)
// if (this.collection) {
// this.collection.off(null, null, this);
// }
}
Then a view is declared as follows..
views.TeamView = Backbone.View.extend({
initialize: function () {
// instantiate this array to ensure sub-view destruction on close()
this.childViews = [];
this.listenTo(this.collection, "add", this.add);
this.listenTo(this.collection, "reset", this.reset);
// storing sub-view as a property will ensure destruction on close()
this.editView = new views.EditView({ model: this.model.edits });
$('#edit', this.el).html(this.editView.render().el);
},
add: function (member) {
var memberView = new views.MemberView({ model: member });
this.childViews.push(memberView); // add child to array
var item = memberView.render().el;
this.$el.append(item);
},
reset: function () {
// manually purge child views upon reset
_.each(this.childViews, function (child) {
child.close();
});
this.childViews = [];
},
// render is called externally and should handle case where collection
// was already populated, as is the case if it is recycled
render: function () {
this.$el.empty();
_.each(this.collection.models, function (member) {
this.add(member);
}, this);
return this;
}
// fired by a prototype extension
closing: function () {
// handle other unbinding needs, here
}
});