JavaScript memory leak error in jQuery plugin

I have a plugin with the following structure:

(function($) { $.fn.drawFieldsTable = function(options) { var settings = { url : 'ajax/pathToFile.php' }; if (options) { settings = $.extend(settings, options); } return this.each(function() { $this = $(this); $.ajax({ url : settings.url, type : 'GET', data: // some request data here success: function(result) { drawFieldElements(result, $this); } }); function drawFieldElements(fields, container) { var replacement = container.clone() .empty(); $.each(fields, function(i) { var field; field = buildFieldItem(i, this); replacement.append(field); }); container.replaceWith(replacement); } function buildFieldItem(i, fieldDataObj) { var $field = $('<div></div>') .addClass('field') .attr('data-fieldname', i) .attr('data-ord', fieldDataObj.ord); return $field; } }); }; })(jQuery); 

I experience a serious memory leak on every call to buildFieldItem . If I do not call this function or make it an empty function or simply do not use its parameters in my area, there is no leak. Leak occurs only in Internet Explorer (8). In other browsers, everything is fine. Help request. Thanks in advance.

UPDATE

I updated my question to better demonstrate what buildFieldItem does. As soon as fieldDataObj can contain about 100 elements, the presence of these 4 lines exclusively causes a fountain, not a leak in IE.

UPDATE 2

I replaced clone () and empty () with the creation of a new div, and I also reduced buildFieldItem to look like this:

 function buildFieldItem(i, fieldDataObj) { var $field = $('<div></div>') .addClass('field'); return $field; } 

Still leaking. The only pattern that does not leak is:

 function buildFieldItem(i, fieldDataObj) { var $field; return $field; } 

Looks like complete nonsense. The less code in the function, the less leakage. Could it be that IE cannot remove the link to the whole function or smth like this?

+4
source share
2 answers

So, I finally found some time to get back to this old question and finish the job. I solved the problem pretty quickly, but I'm just busy.

The following lines cause a memory leak in IE:

 var $isObligatory = $('<input>') .attr('type', 'checkbox') .attr('name', 'is-obligatory') .prop('checked', isObligatory) .change(function() { settings.gFields.applied[settings.subsection][i].obligate = ($(this).prop('checked')) ? 1 : 0; }); 

As you can see, adding a change event handler creates some kind of circular reference. The IE link is not smart enough to break.

The fact is that commenting on this was my first attempt, but as soon as I also had drag-and-drop functionality implemented in the same file, the leak remained, since drag-and-drop caused it too! That is why I struggled with this. Moving draggables outside the plugin code solved it.

Thanks to everyone for posting comments and replies. I'm sorry I didn't post the full code, I thought I was smart enough to post only the problematic lines. Sorry for the late reply too.

0
source

Now that you have provided us with more of the body of the buildFieldItem() function, I see another possible leak.

Without the full code in runnable state, I cannot say for sure, but when you set $field.attr('data-ord', fieldDataObj.ord) , if .ord is any DOM link, you can store some kind of DOM link as an attribute on the $ field object. When you later do .empty() for the parent, this link may not be cleared in older versions of IE and thus it will become stuck and will not be able to receive garbage collection.

This is one of the reasons jQuery came up with the .data() method, because it does not store data on the DOM object, and it cleans up the .empty() method after itself, even in older versions of IE. Since I do not have a working version of the code, I can’t say for sure that this is a problem, but I would suggest changing:

 .attr('data-ord', fieldDataObj.ord); 

to

 .data('ord', fieldDataObj.ord); 

And then change any other links that read this attribute to use .data() to read it.

I also think that you are asking for problems with this line, since you rely on both .clone() and .empty() to be perfect in cleaning everything (even those things that jQuery did not create) and never do not lose anything in any browser:

 var replacement = container.clone().empty(); 

It is much better to create a completely new container, since you want it to be empty, to start with any methods, as this can never leak the previous data, since it does not have:

 var replacement = $("<div>"); 

The answer to the third disclosure of the actual contents of drawFieldElements() :

Now your code really doesn't make sense to me. You have the following:

  function drawFieldElements(fields, container) { var replacement = container.clone() .empty(); $.each(fields, function(i) { var field; field = buildFieldItem(i, this); replacement.append(field); }); container.replaceWith(replacement); } 

You take your container. You clone and empty it. So, now you only have the top level with the class on it remaining in the clone. You add a bunch of content to the clone. Then you replace the original container with the new cloned container. You just ask for memory leak problems with all these extra DOM manipulations.

Why not just clean your original container and add it? Then there is no cloning, no replacement.

  function drawFieldElements(fields, container) { container.empty(); $.each(fields, function(i) { container.append(buildFieldItem(i, this)); }); } 

I have no idea if this helps in a memory leak, but clearing the code to remove all unnecessary DOM manipulations is always a step in a good direction.

+2
source

Source: https://habr.com/ru/post/1387057/


All Articles