Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage collection of unneeded event listeners in javascript

I am building a single page webapp. This means over a period of time I get new DOM elements, remove unneeded ones. For example when I fetch a new form I just replace the contents of a specific div with that form HTML and also set up listeners unique to this form's elements. After some period I replace the contents of this form with a new instance of form (having different ID's).

I set up the event listeners again for this new form. Now the previous form is no longer part of the DOM so I the DOM elements should be automatically garbage collected. I am also expecting the listener functions pointing to the elements removed from the DOM to disappear.

However the following profile gathered from Chrome suggests that my listener count is increasing over time. Can you tell me why this is so? I tried clicking on the "Collect Garbage" button. But this is the profile I get. Is there something wrong with the way I am building my application? Is there a problem and if so how should I fix it?

Chrome snapshot of my webapp for a few minutes

In case it matters I am using JSP templating language with jquery, jquery-ui and some other plugins. This is how the dynamic fragments that I add/remove on my page look like.

<script>
  $(document).ready(function() {
    $("#unique_id").find(".myFormButton").button().click(
      function() {
        $.ajax({url: "myurl.html",
          success: function(response) {
                console.log(response);
          }
        });
    });
  });
</script>

<div id="unique_id">
    <form>
      <input name="myvar" />
      <button class="myFormButton">Submit</button>
    </form>
</div>

Update

If you want to have a look at the actual code here is the relevant portion. This link shows that when clear button is pressed the function clearFindForm is called which effectively refetches content (HTML fragment) using an ajax request and replaces the entire div in this jsp with the content fetched. The refetchContent function works as below: Here is the link to the code in case that helps in giving a better answer.

function refetchContent(url, replaceTarget) {
  $.ajax({
    url: url,
    data: {},
    type: "GET",
    success: function (response) {
       replaceTarget.replaceWith(response);
    },
    error:   function (response) {
       showErrorMessage("Something went wrong. Please try again.");
    }
  });
}
like image 209
Rohit Banga Avatar asked Jan 20 '13 22:01

Rohit Banga


People also ask

Are event listeners garbage collected?

It no longer holds references to signal or controller . With those gone, the browser realises signal can no longer receive events, and that event listener will never be called, so it can be garbage collected along with anything it references.

Is there garbage collection in JavaScript?

Some high-level languages, such as JavaScript, utilize a form of automatic memory management known as garbage collection (GC).

How is garbage collection done in JavaScript?

JavaScript automatically collects the information of the unmercenary memory blocks and removes them from the memory. The garbage collector searches for reachability from the root object to determine whether an object will be used in the future or not.

What happens if you don't remove event listeners?

I'm adding the event listeners inside the created-section. If the page is truly reloaded, they're gone anyway. If that's all you're using them for, removing them will prevent your function from unnecessarily having to execute every time they tap the screen during a pageview.


2 Answers

While jQuery is very good at removing event listeners to DOM elements that are removed via it's methods (including .html() - just read the API: http://api.jquery.com/html/) - it won't remove event listeners to DOM elements that may still have a reference to them in a detached DOM tree.

For example, if you do something like this:

$.ajax({
    ....
})
    .done(function(response,status,jqXHR) {

        //create a detached DOM tree
        form = $(response)

        //add an event listener to the detached tree
        form.find('#someIDInTheResponse').on('submit',function() {

        });

        //add the form to the html
        $('#someID').html(form);
    });

//at some other point in the code
$('#someIDInTheResponse').remove();

Note that in the above example, despite the fact that you removed the element from the DOM, the listener will not be removed from memory. This is because the element still exists in memory in a detached DOM tree accessible via the global variable "form" (this is because I didn't create use "var" to create the initial detached DOM tree in the scope of the done function....there are some nuances and jQuery can't fix bad code, it can only do it's best.

2 other things:

Doing everything inside callbacks or event listeners (like do this on a button click) turns into real bad spaghetti code really fast and becomes unmanageable rather quickly. Try and separate application logic from UI interaction. For example, don't use callbacks to click events to perform a bunch of logic, use callbacks to click events to call functions that perform a bunch of logic.

Second, and somewhat less important, (I welcome feedback on this perspective via comments) I would deem 30MB of memory to be a fairly high baseline for a web app. I've got a pretty intensive google maps web app that hits 30MB after an hour or so of intensive use and you can really notice start to notice it's sluggishness when you do. Lord knows what it would act like if it ever hit 60MB. I'm thinking IE<9 would become virtually unusable at this point, although, like I said, I welcome other people's feedback on this idea.

like image 187
Adam Avatar answered Oct 16 '22 22:10

Adam


I wonder if you are simply not unbinding/removing the previously bound event listeners when you replace fragments?

I briefly looked at the specific sections of code you linked to in your updated question, but didn't see any event listener binding other than what you are doing in document ready, so I'm guessing you are doing some additional binding when you replace the document fragments. I'm not a jQuery expert, but in general binding or assigning additional event listeners does not replace previously bound/assigned event listeners automatically.

My point is that you should look to see if you are doing binding via "click()" (or via some other approach) to existing elements without unbinding the existing event listener first.

You might take a look at moff's answer to this question, which provides an example for click, specifically.

like image 43
ajh158 Avatar answered Oct 16 '22 23:10

ajh158