jQuery doesn't store information about event listeners and data
values with the element itself. This information is instead stored in a global, internal jQuery cache object. Every time you add an event listener or data value to a jQuery object, the jQuery cache gains another entry.
The only way that a jQuery cache entry gets deleted is when you call remove()
on the element that put it there!
Since cache entries also have a pointer back to the element that spawned them, it is easy to create DOM elements that can never be garbage-collected.
Below are some ways to create memory leaks.
Mixing jQuery with native DOM manipulation is dangerous
When you remove element using the browser's native DOM manipulation methods, the jQuery cache is not cleaned up. This leads to DOM elements that are detached from the DOM, but can never be garbage-collected.
E. g. if you use
removeChild()
Show archive.org snapshot
:
var $element = $('<div></div>');
$element.on('click', function() { ... });
$element.appendTo(document.body);
var nativeElement = $element.get(0);
document.body.removeChild(element);
Now the element will remain detached from the DOM and stay in memory forever, even if the $element
and nativeElement
variables go out of scope.
The same applies if you remove elements with innerHTML=
:
var $element = $('<div></div>');
$element.on('click', function() { ... });
$element.appendTo(document.body);
document.body.innerHTML = '';
While setting an element's innerHTML
property to a new string is a very performant operation, it fails to clean up the jQuery cache for any elements that get removed. Again the element will remain detached from the DOM and stay in memory forever, even if the $element
variables go out of scope.
Note that jQuery's
html()
Show archive.org snapshot
method explicitely traverses through all removed elements in order to clean up the jQuery Cache:
$('body').html('');
Detached elements cannot be collected if they have data or event handlers
Let's say that you create a DOM element through jQuery, set a data
attribute on it, but then don't attach it to the DOM:
$element = $('<div></div>');
$element.data('foo', 'bar');
After the last line in the code sample above, $element
can no longer be garbage-collected, even if the $element
variable goes out of scope. This is because the pointer in the jQuery cache blocks its memory from being reclaimed. You need to call $element.remove()
(or remove a parent element), to remove the entry in the jQuery cache and allow $element
to be garbage-collected.
Note that if you do attach $element
to the DOM you can make the fair assumption that will either stay there forever, or that someone will probably remove()
it in the future.
Why oh why doesn't jQuery store event handlers with the DOM element?
You might wonder why the jQuery cache even exists. After all jQuery could simply attach event handlers to the Javascript object that represents the DOM element.
The reason for the existence of the jQuery cache is that old versions of Internet Explorer could not garbage-collect Javascript objects with circular references. For instance, the following object graph could not be reclaimed by old IE's garbage collector if jQuery would attach event handlers to the element:
var $element = $('<div>Close me</div>');
$element.on('click', function() {
$element.remove();
});
Since $element
would now contain a reference to the click handler and vice versa, both elements can no longer be garbage collected even if the variables go out of scope.