Memory leaks in SPA built with Knockout.js
The Problem:
When we build large SPA applications with MV* frameworks that have data bindings, such as Knockout.js, we actually create a connection between our JS models and DOM elements. Those connections may be done in a number of ways, but the bottom line is that DOM elements may have references to our models. In Knockout, for example, every element that has bindings has relation to closure object that stores reference to binding context. This binding context has a pointer to our model. Now, with this in mind, we can see the problem that will come. If we simply remove the DOM element that has bindings, a closure from above will still hold a pointer to our model and as a result, will be never released from memory. This problem is especially critical in SPA applications as to change page we simply swap inner HTML of some container and never do full reload of the page.
Detection:
To detect an above problem in SPA with Knockout we don’t need to do much. If we bind our models and never unbind them, we almost certainly have a leak. However, I’ll show a way to ensure it with Chrome DevTools.
We need to run our application and to open a DevTools (F12 for Windows users). I created a simple application with Knockout and Sammy.js for routing. Download it here. After we run an application we need to take a heap snapshot. Press ‘Profiles’ tab in DevTools, check ‘Take Heap Snapshot’ and press ‘Take Snapshot’
After that change between pages 1 and 2 for 10 times and take another snapshot. Now, when we have these two snapshots, we can compare them. We simply switch the mode of snapshot preview to ‘Comparison’
While in comparison mode, we can sort our results by the ‘# Delta’ field. This field says how many objects from a particular type are living now in memory. You can also check how many new objects were created and how many objects were released by looking at ‘# New’ and ‘# Deleted’ fields respectively. What we are looking for now are the objects that created and never released. In my test application is very simple. We know that we visited each page 5 times, so I will look for objects that have 5 in the ‘Delta’ field.
As we can see, neither VM1 nor VM2 was released during our session.
Solution:
The solution to this problem is very simple. As I said before, we only need to release bindingContext object of the DOM node that we want to remove. Knockout provides us with a number of methods that could be useful in this situation. We can use a ko.cleanNode(element) method before we remove an HTML or we can use ko.removeNode(element), that cleans Node and also removes it. No matter which way we are choosing to use, we need always remember — If we created bindings, we must unbind them!
Top comments (0)