If a Dom Element Is Removed, Are Its Listeners Also Removed from Memory

If a DOM Element is removed, are its listeners also removed from memory?

Modern browsers

Plain JavaScript

If a DOM element which is removed is reference-free (no references pointing to it) then yes - the element itself is picked up by the garbage collector as well as any event handlers/listeners associated with it.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null;
// A reference to 'b' no longer exists
// Therefore the element and any event listeners attached to it are removed.

However; if there are references that still point to said element, the element and its event listeners are retained in memory.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
// A reference to 'b' still exists
// Therefore the element and any associated event listeners are still retained.

jQuery

It would be fair to assume that the relevant methods in jQuery (such as remove()) would function in the exact same way (considering remove() was written using removeChild() for example).

However, this isn't true; the jQuery library actually has an internal method (which is undocumented and in theory could be changed at any time) called cleanData() (here is what this method looks like) which automatically cleans up all the data/events associated with an element upon removal from the DOM (be this via. remove(), empty(), html("") etc).


Older browsers

Older browsers - specifically older versions of IE - are known to have memory leak issues due to event listeners keeping hold of references to the elements they were attached to.

If you want a more in-depth explanation of the causes, patterns and solutions used to fix legacy IE version memory leaks, I fully recommend you read this MSDN article on Understanding and Solving Internet Explorer Leak Patterns.

A few more articles relevant to this:

  • JScript memory leaks
  • Memory leaks in IE8
  • JavaScript Memory Leaks

Manually removing the listeners yourself would probably be a good habit to get into in this case (only if the memory is that vital to your application and you are actually targeting such browsers).

Does removing an element also remove its event listeners?

I made the following test:

<div class="wrapper">
<a href="#">Link</a>
</div>
<script type="text/javascript">
window.onload = function() {
var wrapper = document.querySelector(".wrapper");
var link = document.querySelector("a");
link.addEventListener("click", function() {
console.log("click");
});
setTimeout(function() {
wrapper.innerHTML = "";
}, 4000)
}
</script>

and monitor the results in the dev tools. After the loading of the page the events attached become from 5 to 6. The link is removed from the DOM and the events listeners become 5 again.

Sample Image

What happens to event listeners when element is removed?

Event handlers assigned to destroyed elements are marked for garbage collection. Meaning they are removed from memory. Also, if every one of your events does the same thing, bind them to the encompassing <ul> - click events will bubble up from the lis to the ul.

Also,

"is there a faaar simpler way of doing this other than an event for each <li> element?"

Using a delegated handler attached to the containing ul element makes sense here.
Not necessarily simpler, but only slightly more complicated and
more efficient - or maybe it is simpler if you are dynamically adding li elements.
And it completely avoids the issue you are worried about.

(Courtesy of @nnnnnn)

Converted comment to answer as it seems to have helped answer the question.

Does Javascript remove event handlers of deleted DOM elements?

Yes, modern browsers (eventually) release the memory used by event handlers in DOM nodes. However, old versions of Internet Explorer don't, so it's always good practice to remove event listeners before removing the nodes from the DOM.

This is a good article for understanding what's going on: http://msdn.microsoft.com/en-us/library/bb250448(v=vs.85).aspx

React why should I remove event listeners?

The event listeners need to be removed due to following reason.

  • Avoid memory leaks, if the browser is not handled it properly.Modern browsers will garbage collect event handlers of removed DOM elements but it is not true in cases of legacy browses like IE which will create memory leaks.
  • Avoid collisions of events of components.
  • Remove the side effects when the reference are stored in some persistence such as local storage

Here is a good article to get an insights on event listners

jQuery element is removed from DOM but seems to be stored in memory?

Once a DOM element is removed from the DOM, it is only kept alive if there is javascript code that has a reference to the DOM element. If there are no javascript references to the DOM element itself, then it will be garbage collected by the browser during the next garbage collection sweep.

A reference in javascript means a javascript variable, array, property, etc... that holds the DOM node. In your code above, I don't see any such variables that are lasting so I don't see any reason why the DOM node would not be garbage collected from this particular piece of code.

Why do you think it's being stored in browser memory?

To make sure a DOM element is cleared from memory, you do two things:

  1. Remove it from the DOM.
  2. Make sure no javascript variables hold a reference to it by setting them to null or any other value or making sure they go out of scope so the variables themselves are destroyed (thus releasing their references too).

That's it, there is nothing else.

Do I need to remove event listeners before removing elements?

Just to update the info here. I've been testing various browsers, specifically for memory leaks for circularly dependent event listeners on iframe onload events.

The code used (jsfiddle interferes with memory testing, so use your own server to test this):

<div>
<label>
<input id="eventListenerCheckbox" type="checkbox" /> Clear event listener when removing iframe
</label>
<div>
<button id="startTestButton">Start Test</button>
</div>
</div>

<div>
<pre id="console"></pre>
</div>

<script>

(function() {
var consoleElement = document.getElementById('console');
window.log = function(text) {
consoleElement.innerHTML = consoleElement.innerHTML + '<br>' + text;
};
}());

(function() {
function attachEvent(element, eventName, callback) {
if (element.attachEvent)
{
element.attachEvent(eventName, callback);
}
else
{
element[eventName] = callback;
}
}

function detachEvent(element, eventName, callback) {
if (element.detachEvent)
{
element.detachEvent(eventName, callback);
}
else
{
element[eventName] = null;
}
}

var eventListenerCheckbox = document.getElementById('eventListenerCheckbox');
var startTestButton = document.getElementById('startTestButton');
var iframe;
var generatedOnLoadEvent;

function createOnLoadFunction(iframe) {
var obj = {
increment: 0,
hugeMemory: new Array(100000).join('0') + (new Date().getTime()),
circularReference: iframe
};

return function() {
// window.log('iframe onload called');
obj.increment += 1;
destroy();
};
}

function create() {
// window.log('create called');
iframe = document.createElement('iframe');

generatedOnLoadEvent = createOnLoadFunction(iframe);
attachEvent(iframe, 'onload', generatedOnLoadEvent);

document.body.appendChild(iframe);
}

function destroy() {
// window.log('destroy called');
if (eventListenerCheckbox.checked)
{
detachEvent(iframe, 'onload', generatedOnLoadEvent)
}

document.body.removeChild(iframe);
iframe = null;
generatedOnLoadEvent = null;
}

function startTest() {
var interval = setInterval(function() {
create();
}, 100);

setTimeout(function() {
clearInterval(interval);
window.log('test complete');
}, 10000);
}

attachEvent(startTestButton, 'onclick', startTest);
}());

</script>

If there is no memory leak, the used memory will increase by around 1000kb or less after the tests are run. However, if there is a memory leak, the memory will increase by about 16,000kb. Removing the event listener first always results in lower memory usage (no leaks).

Results:

  • IE6 - memory leak
  • IE7 - memory leak
  • IE8 - no memory leak
  • IE9 - memory leak (???)
  • IE10 - memory leak (???)
  • IE11 - no memory leak
  • Edge (20) - no memory leak
  • Chrome (50) - no memory leak
  • Firefox (46) - hard to say, doesn't leak badly, so maybe just inefficient garbage collector? Finishes with an extra 4MB for no apparent reason.
  • Opera (36) - no memory leak
  • Safari (9) - no memory leak

Conclusion:
Bleeding edge applications can probably get away with not removing event listeners. But I'd still consider it good practice, in spite of the annoyance.

Does jQuery empty detach listeners attached via addEventListener?

empty() removes the child elements from the parent(s). Any event listeners attached to the elements will no longer exist, as the children will no longer exist.

Also: https://api.jquery.com/empty

To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

If you want to remove elements without destroying their data or event handlers (so they can be re-added later), use .detach() instead.

jQuery also cleans up after itself if it knows elements are being destroyed.

However, none of this guarantees that there are still not variable references to the elements in your javascript, which would cause them to not be garbage collected. Neither empty() nor removing the event listeners yourself would cause the element, itself, from being garbage collected. You would have to ensure that your logic no longer has references to the elements being removed.

Is it OK to delete the div id of an element with an event on that div id?

Yes, that's perfectly fine. After it gets removed, the element won't be in the document anymore, which means that the event listener will get garbage collected - nothing wrong with that.

Since you're removing the jQuery collection of #someguidid, there's no need to pass an additional selector to .remove - just call .remove() on the collection:

$("body").prepend(

`<div id="someguidid">

<img src="https://apod.nasa.gov/apod/image/1904/FairbairnCROSSTOCARINA.jpg"/>

</div>`

);

const $elm = $("#someguidid")

$elm.click(function() {

$elm.remove();

});
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>


Related Topics



Leave a reply



Submit