Memory-related issues are the hardest to debug and solve, and unfortunately, they are also the most likely to be encountered when you first start using Backbone. Again, this is not because Backbone itself has memory issues, but because the possibilities that Backbone enables can allow developers to shoot themselves in the foot if they're not careful.
However, before we proceed, it's important to first explain just how browsers manage memory. As you probably already know, the memory in JavaScript is managed by the browser, not the developer, using something called garbage collector. What this means is that you don't have to tell the browser I'm done using this variable. Instead, you can simply stop using that variable and the browser will figure out that it has become garbage
, which will usually make it clean that variable up automatically.
The problem is that the garbage collector operates on a very simple interpretation of what is or is not garbage. In essence, any variable that is not referenced by another variable is considered to be garbage. For instance:
var bookReference = { fakeBook: new Book({title: 'Hamlet, Part 2: The Reckoning'}) }; delete bookReference.fakeBook; // fakeBook has no references, and will be collected as "garbage"
The problem is that developers often don't realize when they leave behind references to a variable, and as such, they force the browser to keep using its memory even though the programmer considers it garbage. Let's look at an example:
var exampleModel = new Backbone.Model(); var exampleView = new Backbone.View(); exampleModel.on('change', exampleView.render); $(document.body).append(exampleView.el); exampleView.remove(); // exampleView will NOT be garbage collected
In this example, it seems like we eliminated all references to exampleView
when we called exampleView.remove()
and removed it from the DOM, but in fact, there was still one reference left behind, hidden inside exampleModel
. This reference was created when we called the on
method of exampleModel
and passed it exampleView.render
. By doing so, we told the Model
to wait until a change happens and then call exampleView.render
, which required it to store a reference to exampleView.render
. Since we didn't delete exampleModel
, this reference remains and won't be garbage-collected, leaving a so-called zombie View
in the browser's memory.
One way to solve this problem would be to remove this reference manually by using the off
method:
example.model.off('change'),
However, having to manage such references can quickly become tedious. Luckily, the creators of Backbone added a method to View
(as well as the other three Backbone classes) that helps solve this problem, called listenTo
. This method works very similarly to on
with two important differences. First, it is called on the listening object (in this case, the View
), rather than on the object being listened to (in this case, the Model
), and second, it does not take a context argument. Instead, the context of the callback will always be set to the object that listenTo
was called on.
Just as there is an off
method for on
, there is a stopListening
method that removes listeners created by listenTo
. However, you won't need to call stopListening
yourself very often, because it's called automatically as part of the remove
method of a View
, which is what makes it so convenient.
Let's retry our last example using listenTo
:
exampleModel = new Backbone.Model();exampleView = new Backbone.View(); exampleModel.listenTo('change', exampleView.render); $(document.body).append(exampleView.el); exampleView.remove(); // exampleView WILL be garbage collected
This time, just as before, we have a View
listening for changes in a Model
. However, because we used listenTo
instead of on
, the reference created as a side effect will get removed whenever stopListening
is called. Since we called remove
on exampleView
and since this method automatically calls stopListening
for us, our View
gets garbage-collected correctly without us having to do any extra work.
Unfortunately, however, listenTo
can't solve all potential leaky references. For one thing, you may still want to use the on
method from time to time. The primary reason for doing so is to listen for events from non-Backbone code, such as a jQuery UI widget. You might also be tempted to use on
because (unlike listenTo
) it takes a context argument, but thanks to Underscore's bind
method, you don't need to do so; you can simply bind your desired context in your callback function before passing it to listenTo
. However, even if you do avoid using on
entirely, you still have to remember to call remove
on your View
. If you don't, you still need to use off
or stopListening
to clear the event binding references. Finally, event handlers aren't the only source of references.
For instance, parent Views
and child Views
often reference each other, and unless you delete the referencing Views
entirely, the Views
that it references won't actually be garbage-collected. The good news is that there's no need to worry too much about such references on a small scale, and in fact, trying to optimize performance too heavily on every last bit of code in your application can wind up being counterproductive. Any given Model
or View
will normally take up only a small amount of memory on its own, so even if you do create a leaky reference that prevents it from being garbage-collected, the actual effect on your application's performance will be minimal. If the user never even notices the leak and then reclaims the memory when he closes his browser or hits refresh, then clearly there was no need for you to have spent time worrying about it.
Instead, you mainly want to focus on managing your references when dealing with large numbers of objects. If you are designing a page View
that will be used throughout your application, or creating a View
for a large table with many separate child Views
, then you will likely want to be extra careful with each reference you create and ensure that all these references get cleaned up when you are done with them.
3.138.134.114