NOTE: This article was written in 2003; the circular-reference memory leak bug described here was fixed in IE shortly after this article was written. This blog archive is for historical purposes; go ahead and use closures today.
JavaScript, as I noted yesterday, is a functional language. That doesn’t mean that it works particularly well (though I hope it does) but rather that it treats functions as first-class objects. Functions can be passed around and assigned to variables just as strings or integers can be.
A reader commented yesterday that “closures are your friends”. Unfortunately there are important situations where closures are not your friends! Let’s talk a bit about those. First off, what’s a closure? Consider the following (contrived and silly, but pedagocially clear) code:
function AddFive(x) { return x + 5; } function AddTen(x) { return x + 10; } var MyFunc; if (whatever) MyFunc = AddFive; else MyFunc = AddTen; print(MyFunc(123)); // Either 133 or 128.
Here we have a typical functional scenario. We’re deciding which function to call based on some runtime test. Now, one could imagine that you’d want to generalize this notion of an “adder function”, and you would not want to have to write dozens and dozens of adders. What we can do is create an adder factory:
function AdderFactory(y) { return function(x){return x + y;} } var MyFunc; if (whatever) MyFunc = AdderFactory(5); else MyFunc = AdderFactory(10); print(MyFunc(123)); // Either 133 or 128.
The anonymous inner function remembers what the value of y
was when it was returned, even though y
has gone away by the time the inner function is called! We say that the inner function is closed over the containing scope, or for short, that the inner function is a closure.
This is an extremely powerful functional language feature, but it is important to not misuse it. There are ways to cause memory-leak-like situations using closures. Here’s an example:
< div id="myMenu" class="menu-bar" > </ div > var menu = document.getElementById('myMenu'); AttachEvent(menu); function AttachEvent(element) { element.attachEvent("onmouseover", mouseHandler); function mouseHandler(){ /* whatever */ } }
Someone has, for whatever reason, nested the handler inside the attacher. This means that the handler is closed over the scope of the caller; the handler keeps around a reference to element
which is equal to menu
, which is that div. But the div has a reference to the handler.
That’s a circular reference.
The garbage collector is a mark-and-sweep collector so you’d think that it would be immune to circular references. But the div isn’t a JavaScript object; it is not in the JavaScript collector, so the circular reference between the div and the handler will not be broken until the browser completely tears down the div. Which never happens.
Doesn’t IE tear down the div when the page is navigated away? Though IE did briefly do that, the application compatibility lab discovered that there were actually web pages that broke when those semantics were implemented. (No, I don’t know the details.) The IE team considers breaking existing web pages that used to work to be worse than leaking a little memory here and there, so they’ve decided to take the hit and leak the memory in this case.
Don’t use closures unless you really need closure semantics. In most cases, non-nested functions are the right way to go.
Reader responses:
How can you say that JavaScript is a functional language?
I take an expansive view of what makes a functional language: it is a language that supports programming in a functional style, not a language that forces you to program in a functional style. Enforcing side-effect free programming like Haskell does is not a requirement of functional languages, and indeed if we made that requirement then many languages that people consider to be clearly functional, like Scheme and OCaml, would have to be considered non-functional.
Pingback: Porting old posts, part 1 | Fabulous adventures in coding