Event handlers, closures and garbage collection in

2020-05-22 00:40发布

问题:

I'm not running into a memory leak in my application yet, but I'm worried about possible problems in the future. I would like to know if doing something like this:

SomeClass.prototype.someMethod= function() {
    var that= this
    this.$div2.click(function() {
        that.someMethod2();
    });
}

And lets say that this.$div2 is appended to another div this.$div1. If I call

this.$div1.remove();

and later loses the reference of my SomeClass instance does the SomeClass instance gets garbage collected? And what about the HTML element this.$div2? this.$div2 would not be inside the DOM because it is appended to this.$div1.

I ask this because the event handler in this.$div2 might keep a reference to the HTML element this.$div2 and also keeps a reference to the instance of SomeClass through the closure because of the variable "that".

So should I care about properly removing all events and HTML elements like this? Or simply removing the "root" element (this.$div1) solves the problem?

回答1:

this.$div2 is appended to this.$div1. If I call this.$div1.remove(); and later lose the reference of my SomeClass instance does the SomeClass instance gets garbage collected?

Yes, when all references to it are lost - also those through event handlers , - the instance can get garbage-collected.

And what about the HTML element this.$div2? this.$div2 would not be inside the DOM because it is appended to this.$div1.

It does not matter whether it is currently attached to the DOM. If some non-collectible object references $div1, it also could access its child node $div2 and that one's event handlers, so the instance referenced from the handler would not be collectible.

I ask this because the event handler in this.$div2 might keep a reference to the HTML element this.$div2 and also keeps a reference to the instance of SomeClass through the closure because of the variable "that".

That's a circular reference and should get handled well by the engines (when none of the objects inside the circle is referenced from outside it can get collected). However, (old?) Internet Explorers fail to do this when a DOM object is involved in the circle.

For that reason the .remove jQuery method (code) internally calls the (internal) cleanData method which detaches all event listeners.

So should I care about properly removing all events and HTML elements like this? Or simply removing the "root" element (this.$div1) solves the problem?

Yes, calling remove on a jQuery wrapper automatically removes all events (from all child elements) and DOM nodes.



回答2:

Should I care about properly removing all events and HTML elements like this?

The short answer is No! at least in 99% of the cases, it will not matter in any way because the memory used by one DOM element is trivial compared to the overall memory used by a web page.

However it is always a good practice to release the memory used by disposing unneeded objects, but you cannot say that GC would definitely releases the memory utilized by the elements because garbage collection is entirely up to the browser! In theory GC should only kick in when there are no references to the DOM element, at least that's how Chrome works, but in languages like JavaScript, you don't explicitly tell the run-time you're done with the object, things get messy in JavaScript so quickly: a function might pass the object on to some more functions, the object might get saved away as a member within yet another object, an object might get referenced through the closure etc, so it's completely up to the browser how and what to collect!

In your case removing div1 frees the html document and the element would not render in the view, in fact jQuery's remove method takes care of removing all the events, expando properties, and child elements attached to the element together with the element itself, however you keep a reference of div1 and div2 in yet another object making both DOM elements Orphan elements! removing SomeClass instance variable releases all references to the DOM elements making them candidate for garbage collection but here comes the tricky that variable that causes the DOM element make a reference to the instance of SomeClass through clusure! This issue is known as Circular Reference in IE:

JavaScript Objects and DOM elements that store references to one another cause Internet Explorer’s garbage collector to not reclaim memory, resulting in memory leaks

You can read more about it here

This particular leak is mostly of historical interest IE<8, but a good example of breaking circular links is to avoid using the variable that, instead use proxy or delegate to change the event handler's context to some particular context.

ECMA 5th bind method is quit useful changing contexts when in comes to DOM event handlers, here's a simple handler based on your code without using variable closure:

this.$div2.click((function() {
        this.someMethod2();
    }).bind(this));


回答3:

If you will create element dynamically, then assign to them events. i think that your code is not a good way to do that. you should follow this manner:

for fixed elements if you need an event, use these two functions; the first called in the constructor, the second in the destructor.

on_Events: function() {
   $('your_form').on('event_name', {element_Selector}, callback_function)
},
off_Events: function() {
   $('your_form').off('event_name', {element_Selector}, callback_function)
}

for dynamically objects. add events when creating an element and remove these events just before destroying the element.