简体   繁体   中英

javascript memory leak in IE6 with basic jquery ui plugin

First, I read and researched extensively on this issue. There is already a ticket with jquery UI which I'm following. And I know a way around this bug but I'm very curious as to why this occurs. I believe the bug is due to a closure but my javascript-fu isn't expert.

I figure that the jquery UI team has better things to do than spend energy on an IE6 bug. So I wanted to bring this out to the general javascript public.

The following is a test case:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>jquery ui memory leak test</title>
    <script type="text/javascript" src="jquery-1.5.js"></script>
    <script type="text/javascript" src="jquery.ui.widget.js"></script>
    <script type="text/javascript">
    (function($) {    
        $.widget("ui.test", {
            _create: function() {
            }
        });

        $(document).ready(function() {
            for (var i = 0; i < 1; i++) {
                $("#container").append("<div id='inner'></div>");
                $("#inner").test();
                $("#inner").test("destroy");
                $("#container").empty();
            };
        });
    })(jQuery);
    </script>
</head>
<body>
    <div id="container">
    </div>
</body>
</html>

I've tested using all combinations of jquery 1.4.4 and 1.5 and jquery-ui-1.8.9 and jquery-ui master (as of writing) but they all produce the same result.

My test widget is I believe the simplest you can get.

If you test using sIEve , you can spot the leak. Otherwise, increase the counter to something like 1000, and you will see the memory increase pretty easily. There is another tool from Microsoft you can use to detect leak as well.

So the leak is due to the custom event binding in the widget's _createWidget method:

var self = this;
this.element.bind( "remove." + this.widgetName, function() {
    self.destroy();
});

So, if I comment those out, there is no leak. I am using 1.8.9 rather than master since 1.8.9's widget code seems simpler (master has changed a bit).

Now, if I bind the same event outside of the widget altogether, there is also no leak. For example, I'd insert the following code after I create the widget but before destroy:

$("#inner").bind("remove.test", function() {});

I purposefully added a no-op function but it doesn't matter what's inside the callback function. You could argue, since I am doing destroy manually afterward, the binding isn't necessary. But that isn't the point.

So my question is why does the original code, the binding call from within the widget code leak? My suspicion is that it's due to a closure but I can't explain it.

Can someone explain this?

As I understand it the problem happens when there is a circular reference between JS and the DOM, when a JS variable points to a DOM object, and that DOM object has a property (or, usually, event handler) pointing back to the JS variable. Your example above with the .bind() seems to do this. Apparently IE uses reference counting in its garbage collection process and circular references don't get collected.

I've seen at least a couple of MSDN blogs that blamed this on JavaScript and basically recommended avoiding closures, which obviously isn't very helpful, but here are some non Microsoft/MSDN articles that discuss the problem and give some workarounds:

http://laurens.vd.oever.nl/weblog/items2005/closures/

http://www.ibm.com/developerworks/web/library/wa-memleak/

JavaScript Closures and Memory Leaks

I'm sorry that I can't provide an explanation to the cause of this problem, but I worked on a workaround to prevent this rather annoying leak to occur.

After including jquery and jquery-ui in my page I added the following code:

var origCreateWidget = $.Widget.prototype._createWidget;
$.Widget.prototype._createWidget = function( options, element ) {
   var origBind = $.fn.bind;
   var widget = this;
   $.fn.bind = function( type, func ) {
      if( typeof( type ) === "string" && type.indexOf( "remove." ) === 0) {
         // ignore the remove events
      }
      else {
         origBind.apply( this, arguments );
      }
      return this;
   }

   var res = origCreateWidget.call( this, options, element );

   $.fn.bind = origBind;
   return res;
};

Additionally I call destroy when the window unload event occurs. After these changes sIEve doesn't report any leaks and the windows task manager shows a constant memory consumption while navigating through pages containing a sortable widget.

After hours of searching and testing I still couldn't figure out the real cause for the leak.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM