Memory leak risk in JavaScript closures
I used to work with the ex-program manager for JavaScript within Microsoft, on a non-browser EcmaScript (err.. JScr... JavaScript) project. We had some lengthy discussions about closures. In the end, the point is that they are harder to GC, not impossible. I'd have to read DC's discussion of how MS is 'wrong' that closures cause memory leaks -- for in IE's older implementations, closures were certainly problematic, because they were very hard to garbage collect with the MS implementation. I find it strange that a Yahoo guy would try to tell the MS architects that a known issue with their code was somewhere else. As much as I appreciate his work, I don't see what basis he had there.
Remember, the article you reference above refers to IE6, as IE7 was still under heavy development at the time of its writing.
As an aside -- thank god, IE6 is dead (don't make my dig up the funeral pictures). Although, don't forget its legacy... I've yet to see anyone make a credible argument that it wasn't the finest browser in the world on day 1 of its release -- the problem was that they won the browser war. And so, in what amounts to one of the larger blunders of their history -- they fired the team afterwards and it sat stagnant for nearly 5 years. The IE team was down to 4 or 5 guys doing bug fixes for years, creating a huge brain drain and falling behind the curve dramatically. By the time they rehired the team and realized where they were, they were years behind because of the added cruft of dealing with a monolothic codebase that nobody really understood anymore. That's my perspective as an internal in the company, but not directly tied to that team.
Remember too, IE never optimized for closures because there was no ProtoypeJS (heck, there was no Rails), and jQuery was barely a glimmer in Resig's head.
At the time of the writing, they were also still targeting machines with 256 megs of RAM, which also hadn't been end-of-life'd.
I thought it only fair to hand you this history lesson, after making me read your entire book of a question.
In the end, my point is that you're referencing material which is hugely dated. Yes, avoid closures in IE6, as they cause memory leaks -- but what didn't in IE6?
In the end, it is a problem that MS has addressed and continues to address. You're going to make some level of closures, that was the case, even back then.
I know that they did heavy work in this area around IE8 (as my unmentionable project used the non-at-the-time standard JavaScript engine), and that work has continued into IE9/10. StatCounter (http://gs.statcounter.com/) suggests that IE7 is down to a 1.5% market share, down from 6% a year ago, and in developing 'new' sites, IE7 becomes less and less relevant. You can also develop for NetScape 2.0, which introduced JavaScript support, but that would be only slightly less silly.
Really... Don't try to over-optimize for the sake of an engine which doesn't exist anymore.
Right, after spending some time with that IEJSLeaksDetector
tool, I've found out that the things I talked about in my original question DO NOT CAUSE MEM LEAKS. However, there was 1 leak that did pop up. Thankfully, I've managed to find a solution:
I have a main script, and at the bottom, there's an old school:
window.onload = function()
{
//do some stuff, get URI, and call:
this['_init' + uri[0].ucFirst()](uri);//calls func like _initController
//ucFirst is an augmentation of the String.prototype
}
This causes a leak in IE8, That I couldn't fix with a window.onbeforeunload
handler. It seems you have to avoid binding the handler to the global object. The solution lies in closures and event listeners, it's a bit of a faff, but here's what I ended up doing:
(function(go)
{//use closure to avoid leaking issue in IE
function loader()
{
var uri = location.href.split(location.host)[1].split('/');
//do stuff
if (this['_init' + uri[0].ucFirst()] instanceof Function)
{
this['_init' + uri[0].ucFirst()](uri);
}
if (!(this.removeEventListener))
{
this.detachEvent('onload',loader);//(fix leak?
return null;
}
this.removeEventListener('load',loader,false);
}
if (!(go.addEventListener))
{
go.attachEvent('onload',loader);//(IE...
}
else
{
go.addEventListener('load',loader,false);
}
})(window);
That way, the (on)load event is unbound just before the window.load
handler returns, according to the IEJSLeaksDetector tool, there are NO leaks in my application. I'm happy with that. I hope this snippet is of some use to one of you - if someone has suggestions to improve this approach, don't hesitate to do so!
Cheers, and thanks to all of you who went through the trouble of reading and trying my dribble above!
PS: in case someone cares, here's the ucFirst String method:
if (!(String.prototype.ucFirst))
{
String.prototype.ucFirst = function()
{
"use strict";
return this.charAt(0).toUpperCase() + this.slice(1);
};
}