10

I've been building a large single page application and recently got into exploring memory leaks in JS. And I think I've got a memory leak because - as I use the Profiles (Snapshot) function in Chrome - I see that I have a lot of detached DOM elements.

Here is a simplified view of my setup:

<div id="container">
  <div class="buttons">
    <a class=".btn" href="/someurl/"> Button A</a>
    <a class=".btn" href="/someurl/"> Button B</a>
    <a class=".btn" href="/someurl/"> Button C</a>
  </div>

  <div class="ajaxHolder"></div>
</div>

So if a user clicks Button A, for example, I would use an AJAX call to load content into the .ajaxHolder. Something like this:

//This is the content...
<div class="contentA">
  <p>some text...</p>
  <input type="checkbox" class="checkbox">
  <input type="checkbox" class="checkbox">
  <input type="checkbox" class="checkbox">
  <input type="checkbox" class="checkbox">
</div>

I also have two functions inside my MAIN script file. One would be like this:

//Click event bound to a.btn which tigger the ajax call
$(.buttons).on('click', '.btn', function(){ 
  //Do ajax here...
  //on success...
  var holder = $(".ajaxHolder");
  holder.children().off().remove(); // Is this the way to do this??
  holder.off().empty(); //I don't think I need .off() here, but I guess it can't hurt...
  holder.append(ajaxSuccessData);
});

So far so good. But now with the content loaded, I also have this function inside my MAIN script file:

//So here, I am binding an event to the checkboxes...
$(".ajaxHolder").on('change', '.checkbox', function(){
  //Do something...
});

So now if the user presses button B, for example, .ajaxHolder is emptied, but all those events tied to my checkboxes seem to stick around, because they are showing up in my detached DOM tree.

What am I missing here? How can I make sure I don't have any detached DOM elements, in a single page application running like this?

BONUS QUESTION: I have A LOT of events tied like this:

$(".ajaxHolder").on('someEvent...','.someClass....', someFunction());

That is to say, everything is always attached to my .ajaxHolder, because I have to use event delegation since .ajaxHolder will always exist - whereas other items that are loaded will not always exist, so I cannot simply do $(button).on('click')..., etc.

So is this the correct way to do this as well? Is there a limit to how many things I can attach to this .ajaxHolder or are there no issues with doing this?

EDIT: Here is an image of my console, if that helps at all.

enter image description here

Amir
  • 4,211
  • 4
  • 23
  • 41
  • 2
    removing the element with a jQuery method is more than enough to remove all associated data, as long as the data was associated using jQuery. You don't need to manually unbind events. – Kevin B Feb 17 '16 at 20:27
  • You can do delegation on the ajaxHolder all you want, however, i find that to easily get out of hand as the project grows. It can also begin to hinder performance if you have a lot of event handlers bound to it of the same type, however you'll likely run into maintainability issues long before you start to have that performance problem. – Kevin B Feb 17 '16 at 20:28
  • @KevinB So, why are the detached dom elements showing up? Any other cause that would be keeping a reference to items, that have been removed? And, for the second part, what other option do I have, other than delegating it to the ajaxHolder... I guess I could delegate it to the document, but that's just more travel and doesn't solve the issue. What's your solution? – Amir Feb 17 '16 at 20:30
  • Both jQuery `remove` and `empty` does what Kevin says, they also remove all associated data and events, and do a good job of cleaning up whatever you attached using jQuery etc. – adeneo Feb 17 '16 at 20:31
  • There really isn't a great solution to this problem (other than using something like react/angular etc). Binding the events each time a sub page is loaded can be difficult to manage, and so can having delegated events bound globally, both have pitfalls. what you ***don't*** want to do is include js in the html that you are loading for each sub page. – Kevin B Feb 17 '16 at 20:33
  • as far as the detached elements... check this out: http://stackoverflow.com/questions/16901759/javascript-memory-leaks-detached-dom-tree – Kevin B Feb 17 '16 at 20:39
  • @KevinB Thanks Kevin, I've actually found that link and others before making this post. I've been troubleshooting and thought, that maybe I had found my issue, in that I wasn't unbinding events correctly. But apparently I am, so I'm at a loss now how to continue troubleshooting. I've attached a picture of my console, can you point me in the right direction on what I need to look at there to pinpoint, what is causing these elements to remain bound? – Amir Feb 17 '16 at 21:15

3 Answers3

1

Not sure this is still relevant, but from the console image I gather you are using bootstrap tooltips, and they seem to be holding the reference to the DOM node. I suggest you call the destroy method via the API

thedude
  • 9,388
  • 1
  • 29
  • 30
-1

jQuery event unbinding and removing of elements can be dong with the following methods:

$.off()

$.remove()

Sators
  • 2,746
  • 1
  • 20
  • 26
  • 1) don**e**; 2) This is not really an answer. It's a pointer to two links on the web. If you want to improve this, explain how the OP would use those functions to accomplish their goal. Note that the code shown in the question already has both `.off()` and `.remove()`. – Heretic Monkey Aug 22 '16 at 23:26
-3

The best method to do this would be to unbind all the event listeners by using $(selector).off(); method and then removing the element by using $(selector).remove();

It would stop all the memory leaks happening because of the event handlers.