Having trouble tracking down and dealing with all those Backbone leaks? Coccyx gives you two things to help avoid and track down leaks:
Coccyx is emphatically not a Backbone.js framework. It is simply a set of Backbone opinions intended to help you deal with Backbone leaks!
There are two versions of Coccyx: an obtrusive version (documented below) that monkey-patches Backbone and an unobtrusive version (in case monkey-patching isn't your style) documented at the end of this readme.
Views are only garbage collected when their reference counts drop to zero. This cannot happen until all event bindings pointing to callbacks on the view are unbound. Coccyx adds the tearDown
method to all Backbone views. When you're done with a view
and want to make sure it is garbage collected, simply call
view.tearDown();
This does the following things:
- Remove any event callbacks bound via Backbone's
Event.on
orEvent.bind
undelegateEvents()
- Call
view.beforeTearDown()
(if such a method exists) - Call tearDown on any subViews (see "Tearing Down SubViews" below for more on adding/removing subviews)
- Remove
view.$el
from the DOM
Cleaning up Backbone event bindings only works on Backbone version > 0.9.2. If you have an earlier version of Backbone you must upgrade for Coccyx to work correctly.
Coccyx automatically cleans up any Backbone event bindings on tearDown
. To do this in obtrusive mode, Coccyx injects code into Backbone's on
and bind
methods to allow views to track which event bindings need to be cleaned up.
For this mechanism to work you must pass the view
in as the context when using Backbone's on
method:
model.on('change', view.callback, view);
This has the added benefit that you do not need to remember to _.bind(view.callback, view)
You can enforce this convention by setting Coccyx.enforceContextualBinding
to true
. Coccyx will then throw an exception if an event binding is attempted (anywhere) without passing in a context.
Note: For performance considerations, calling
off
orunbind
does not untrack the event binding. To be clear: the unbinding will take place and the associated callback will no longer be called when the event fires, however the internal data structure that Coccyx uses to track which dispatchers need to be unbound duringtearDown
does not change. This means that a reference to the dispatcher will exist on the view even afteroff
is called. This, ironically, will result in a memory leak untiltearDown
is called.
To completely untrack an event binding you must call
view.unregisterEventDispatcher(object)
with the Backbone object that you calledon
orbind
on.unregisterEventDispatcher
will automatically calloff
for you.
Note that only view contexts keep track of dispatchers in this way. You don't have to worry about other contexts (models, collections, whatever) hanging on to references to your event dispatchers.
For the majority of use cases this proviso is a non-issue -- but now you know.
Coccyx calls Backbone's view.undelegateEvents
to clear out DOM event bindings. Therefore, you must bind events using either the events
hash or the delegateEvents
method.
Views will sometimes have clean up work to do that Coccyx does not automatically handle. A common example involves DOM event bindings that are not appropriate for delegateEvents
. In such instances you should add a custom beforeTearDown
method to your Backbone view and do the cleanup there. Coccyx will call this method if it exists. Here's an example usecase:
MyView = Backbone.View.extend({
initialize: function() {
this.boundResizeHandler = _.bind(this.resizeHandler, this);
$(window).on('resize', this.boundResizeHandler);
},
beforeTearDown: function() {
$(window).off('resize', this.boundResizeHandler);
},
resizeHandler: function() {
...
}
})
Perhaps you have beforeTearDown
code that is shared across all your views, but you don't want to, or can't, pull out this shared behavior into a common superclass. You can register any number of callbacks to be called on each view upon tearDown
by passing callbacks to:
Coccyx.addTearDownCallback(function() {
// do your own cleanup here
});
the context (this
) of the callback function is the view being torn down. These global tear down callbacks are called right after the view's beforeTeardown
callback. Coccyx.addTearDownCallback
applies globally and, is therefore, rather smelly -- use sparingly and with care!
The most useful aspect of tearDown
is the fact that it will recursively call tearDown
on all subviews associated with the view. This makes it very easy to ensure that entire Backbone view hierarchies are cleaned up simply by calling tearDown
on the root node of the hierarchy.
For tearDown
to know what a view's subviews are you must pass any Backbone subView
s to the view
via:
view.registerSubView(subView);
registerSubView
returns the passed in subView
If you are removing a subView
by calling subView.tearDown()
there is no need to unregister the subview. Otherwise you must:
view.unregisterSubView(subView);
when removing a subview. (You should rarely need to do this: tearDown
is your friend!)
It is often convenient to be able to tear down all of a view's subviews, but leave the view itself alone. This is commonly done in render
methods that blow away all the view's content and then regenerate it. You can tear down all registered subviews by calling:
view.tearDownRegisteredSubViews();
Sick and tired of seeing child
printed out when you console.log a backbone object? This minor annoyance becomes a serious concern when trying to use Chrome's excellent heap profiler to find leaks and analyze their retaining tree -- which of those many child
s is the object you're looking for?
Coccyx solves this problem by providing the constructorName
property. Simple pass a descriptive class name in for constructorName
to your Model, Collection, View or Router and see it appear on the console and in the heap profiler. Since the heap profiler allows you to search by constructor name you can very quickly find objects of concern and make sure they are getting correctly cleaned up.
Here's an example:
var AnimalModel = Backbone.Model.extend({
constructorName: 'AnimalModel'
});
var dog = new AnimalModel({name: 'bagel'});
console.log(dog);
> ▶ AnimalModel
You can enforce the use of constructorName
by setting Coccyx.enforceConstructorName
to true
. Coccyx will then throw an exception if a new class is crated without supplying a constructorName
.
Note: Underscore's bindAll
method works by iterating over all functions on an object and wrapping them in anonymous closures. This includes the constructor function which means, unfortunately, that your object will lose its constructorName. Best to avoid bindAll
and actually pay attention to where you need to bind methods. Alternatively... you could monkey patch Underscore...
JasmineCoccyx.js
provides two custom Jasmine matchers to support test-driving code that uses Coccyx. These are:
expect(view).toHaveBeenTornDown()
asserts that a view has been torn down.expect(view).toHaveRegisteredSubView(subview)
asserts thatsubview
is a registered subview ofview
.
Just be sure to include JasmineCoccyx.js
in your Jasmine suite to install this matchers. JasmineCoccyx.js
works with both the obtrusive and unobtrusive versions of Coccyx.
As of version 0.4 there are two versions of Coccyx: Coccyx.js
and UnobtrusiveCoccyx.js
. Coccyx.js
is obtrusive in that it monkey-patches Backbone to add named constructors to all Backbone objects, hierarchy management to all Backbone views, and automatic binding tracking to all objects that mixin Backbone.Event
(this includes all models and collections). All you need to do to reap Coccyx's benefits is registerSubviews
and tearDown
your existing Backbone views.
Obtrusive Coccyx (Coccyx.js
) is the preferred version as it emphasizes that Coccyx is not a Backbone framework but rather a set of memory management opinions that should be applied and enforced across your entire app.
If, however, you prefer that Coccyx leave Backbone untouched you should use unobtrusive Coccyx:
Instead of including Coccyx.js
include UnobtrusiveCoccyx.js
. Now only subclasses of Coccyx.View
will be able to registerSubviews
and respond to tearDown
.
More importantly, only bindings to subclasses of Coccyx.Model
and Coccyx.Collection
(or any object that mixes in Coccyx.Events
) will be tracked and automatically unbound when tearDown
is called. This means that it is not sufficient to turn your views into Coccyx views. You must also convert any Backbone models and collections that views bind to into Coccyx models and collections.
Finally, in unobtrusive Coccyx, only objects that inherit from Coccyx support the constructorName
property.
Coccyx requires:
- Backbone (duh) (tested with 0.9.2, requires at least version 0.9.2 -- Coccyx does not work with older versions of Backbone)
- Underscore (tested with 1.3.3. Note: 1.4.0 and 1.4.1 include a regression that causes Coccyx to crash when tearing down views with no subviews. Please upgrade to 1.4.2.)
To use Coccyx you must include Coccyx.js
or UnobtrusiveCoccyx.js
after including Undersocre and Backbone.
Future changes to backbone could break Coccyx or obviate its need. If the latter happens - great! If the former: let me know and I'll try to ensure compatibility going forward.
...check out Cocktail. Cocktail helps you DRY up your backbone code with mixins.