Closures seem to frighten people a bit. Partially I suspect this is down to the academic nature of the term "closure". It sounds like something Californians look to achieve, not something to help you write software. (Disclosure: I mostly grew up in San Francisco, so I'm allowed to poke fun at my fellow Californians. Don't Try This At Home.) I suspect it's also because if you don't know the rules for them, they seem mysterious, and the mysterious is often frightening.
First off, what is a closure? I'll leave a thorough definition to my academic betters, but let's put it this way for my fellow plodders and I: A closure is a function with data intrinsically bound to it.
Consider this code:
function updateDisplay(panel, contentId)
url = 'getcontent?id=' + contentId;
'Error retrieving content ID '
+ ' from "'
+ '", error: '
This updates a panel element based on the results of a call to retrieve some JSON data. The call to the
jsonRequest function accepts two parameters: The URL that will return the JSON data, and a callback function to trigger when the request completes (one way or the other). If the data was returned successfully, the callback sets the content of the panel from the JSON data and makes sure that the "error" CSS class is not set on the element; if there's an error, it shows details about the error and sets the "error" CSS class. (In this example, we happen to be using Prototype to extend our panel element with the nifty class name and update methods, but that's as much to keep the example simple as anything else.)
The callback above is an example of a closure: A function with data bound to it, in this case the
contentId arguments we passed into the
updateDisplay function, and also
url local variable. It's useful to have this information bound to the callback, because the
jsonRequest function doesn't know anything about
contentId, it just knows about triggering the callback — but the callback has the information it needs to do its job.
I'm lazy and it's less typingI find it clearer).
- Closures do not create memory leaks. Someone probably told you that they did at some point (perhaps they thought that's what Microsoft was trying to say here).
#1: Everything is a object
- A property called
argumentswhich is an array (of sorts, in most implementations it's not actually an Array object) of the actual arguments we called the function with, plus a
calleeproperty which is a reference to the function being called.
- A property for every declared variable within the function (e.g., every "var" statement). (These start out with the value
Let's look again at selected bits of the
updateDisplay function from earlier:
function updateDisplay(panel, contentId)
And let's assume we call it with a reference to a 'leftPanel' div and a contentId of 123:
That creates a call object for this execution of
updateDisplay that essentially looks like this:
call.arguments = [leftPanel, 123]
call.arguments.callee = updateDisplay
call.panel = leftPanel
call.contentId = 123
call.url = undefined
This object is then used within the body of the function when the code uses the arguments
contentId, or the local variable
"But wait," you say. "I don't refer to an object when I use the arguments or variables in my function, I just use their names." Indeed — the use of the call object is implicit. How do we end up using the call object when we just write "contentId" (for instance)? Because once the call object is created, it's put at the top of the scope chain for the duration of the function call. Which takes us nicely to...
#2: Variable names are resolved using a "scope chain"
document object, and perhaps the global
navigator object as well. Here's the thing: Those aren't global objects. Those are properties of a global object — in fact, of the global object. The
navigator properties are set up for you by the browser, but they're just properties of an object.
document.writeln("Blah blah blah"), eventually the
document property is found on the global object and used.
window. In browsers, the
window object is the global object; it just also has a property, "
window", that refers to itself.)
So quick: Within a function, how do variable names get resolved? Right! When the function is being executed, the call object with all those nifty properties for the function's arguments and variables is put at the top of the scope chain. So during our call to
updateDisplay from earlier, the call object for the call is at the top of the scope chain, followed by the global object, like this:
When the interpreter sees a variable reference, say
contentId, it looks on the call object: If there's a
contentId property on the call object, it gets used; otherwise, the interpreter looks at the next object in the scope chain, which in this case is the global object.
(There's more to know about the scope chain than I've described here; the astute reader will be wondering, for instance, how objects and their instance members come into play, or what the
with statement does to things. Alas, we can't tackle everything all at once...)
But how can I know when I'm writing
updateDisplay that the scope chain will look like that? I mean, doesn't it depend on who's calling the function? Nope. And that brings us to our next point:
Okay, so we get the concept of the call object, which is created when we call a function; and we get the global object, which sits at the bottom of the scope chain to handle globals. But what's this "lexically scoped" thing? It's just a fancy way of saying that a function's scope is determined by where it's defined (e.g., the text defining it; léxis is Greek for "word" or "speech"), not where it's called.
Let's put that another way: When a function is defined, the code defining it is in some kind of context — a function is running, or the page itself is running if you've done the code at the top level. That context has an active scope chain when the function is defined. So when creating the function object, the interpreter creates a property on it (called [[Scope]] in the ECMA spec, but you can't access it directly) with a reference to the active execution context's scope chain. Even when the context goes away (e.g., the function returns), because the function object created by the definition has a reference to the scope chain, the scope chain isn't garbage collected — it's kept alive by the active reference to it from our function object. (Assuming that we've kept a reference to the function object, as we did in our example by passing it into the
jsonRequest method; otherwise, the function and the scope chain are both garbage-collected since nothing references them.)
Remember our closure at the beginning of this post? It gets a [[Scope]] property pointing to the scope chain in effect when
updateDisplay was called with
So when we call it, first its scope chain is put in place, then the call object for this particular call to the function is put on top of the scope chain, and the function is executed with this chain:
And there we are, the closure can access
panel and all of the other properties of the call object for the call to
updateDisplay because they're on the scope chain. They're on the scope chain because that was stuck onto the closure's function object when it was created. No magic. Just objects, the scope chain, and lexical scoping all working together.
#4: Closures don't cause memory leaks
So why do people think closures cause memory leaks? A couple of reasons, I suspect, but chief among them being
Lest I be accused of Microsoft-bashing, I should point out that IE is not the only browser that sometimes loses track of things; it's just by far the worst. Firefox 2 has a bad habit of leaking a bit of memory on XMLHttpRequest calls, which also frequently involve closures. The good news there is that Firefox 3 beta 3 is looking awfully good indeed on this front.
Separate from browser issues, though, if you're not really clear on how closures work, particularly with regard to the scope chain, you'll miss the fact that a closure keeps a reference to all of the variables and arguments in scope where it's created, not just the ones it uses; and so if you have (say) a massive temporary array allocated in that scope that the closure doesn't use, you might be tempted to say that the closure is causing the array's memory to leak. (The answer there is simple: Clear the array's variable when you're done with it.)
Oh! I said I'd tell you something at the end that would surprise you for about three seconds before you said "Oh, of course." Here it is: