8.8. Function Scope and ClosuresAs described in Chapter 4, the body of a JavaScript function executes in a local scope that differs from the global scope. This section explains these and related scoping issues, including closures.[*]
8.8.1. Lexical ScopingFunctions in JavaScript are lexically rather than dynamically scoped. This means that they run in the scope in which they are defined, not the scope from which they are executed. When a function is defined, the current scope chain is saved and becomes part of the internal state of the function. At the top level, the scope chain simply consists of the global object, and lexical scoping is not particularly relevant. When you define a nested function, however, the scope chain includes the containing function. This means that nested functions can access all of the arguments and local variables of the containing function. Note that although the scope chain is fixed when a function is defined, the properties defined in that scope chain are not fixed. The scope chain is "live," and a function has access to whatever bindings are current when it is invoked. 8.8.2. The Call ObjectWhen the JavaScript interpreter invokes a function, it first sets the scope to the scope chain that was in effect when the function was defined. Next, it adds a new object, known as the call object (the ECMAScript specification uses the term activation object) to the front of the scope chain. The call object is initialized with a property named arguments that refers to the Arguments object for the function. Named parameters of the function are added to the call object next. Any local variables declared with the var statement are also defined within this object. Since this call object is at the head of the scope chain, local variables, function parameters, and the Arguments object are all in scope within the function. This also means that they hide any properties with the same name that are further up the scope chain, of course. Note that unlike arguments, this is a keyword, not a property in the call object. 8.8.3. The Call Object as a NamespaceIt is sometimes useful to define a function simply to create a call object that acts as a temporary namespace in which you can define variables and create properties without corrupting the global namespace. Suppose, for example, you have a file of JavaScript code that you want to use in a number of different JavaScript programs (or, for client-side JavaScript, on a number of different web pages). Assume that this code, like most code, defines variables to store the intermediate results of its computation. The problem is that since this code will be used in many different programs, you don't know whether the variables it creates will conflict with variables used by the programs that import it. The solution, of course, is to put the code into a function and then invoke the function. This way, variables are defined in the call object of the function: function init() { // Code goes here. // Any variables declared become properties of the call // object instead of cluttering up the global namespace. } init(); // But don't forget to invoke the function! The code adds only a single property to the global namespace: the property "init", which refers to the function. If defining even a single property is too much, you can define and invoke an anonymous function in a single expression. The code for this JavaScript idiom looks like this: (function() { // This function has no name. // Code goes here. // Any variables declared become properties of the call // object instead of cluttering up the global namespace. })(); // end the function literal and invoke it now. Note that the parentheses around the function literal are required by JavaScript syntax. 8.8.4. Nested Functions as ClosuresThe facts that JavaScript allows nested functions, allows functions to be used as data, and uses lexical scoping interact to create surprising and powerful effects. To begin exploring this, consider a function g defined within a function f. When f is invoked, the scope chain consists of the call object for that invocation of f followed by the global object. g is defined within f, so this scope chain is saved as part of the definition of g. When g is invoked, the scope chain includes three objects: its own call object, the call object of f, and the global object. Nested functions are perfectly understandable when they are invoked in the same lexical scope in which they are defined. For example, the following code does not do anything particularly surprising: var x = "global"; function f() { var x = "local"; function g() { alert(x); } g(); } f(); // Calling this function displays "local" In JavaScript, however, functions are data just like any other value, so they can be returned from functions, assigned to object properties, stored in arrays, and so on. This does not cause anything particularly surprising either, except when nested functions are involved. Consider the following code, which includes a function that returns a nested function. Each time it is called, it returns a function. The JavaScript code of the returned function is always the same, but the scope in which it is created differs slightly on each invocation because the values of the arguments to the outer function differ on each invocation. (That is, there is a different call object on the scope chain for each invocation of the outer function.) If you save the returned functions in an array and then invoke each one, you'll see that each returns a different value. Since each function consists of identical JavaScript code, and each is invoked from exactly the same scope, the only factor that could be causing the differing return values is the scope in which the functions were defined: // This function returns a function each time it is called // The scope in which the function is defined differs for each call function makefunc(x) { return function() { return x; } } // Call makefunc() several times, and save the results in an array: var a = [makefunc(0), makefunc(1), makefunc(2)]; // Now call these functions and display their values. // Although the body of each function is the same, the scope is // different, and each call returns a different value: alert(a[0]()); // Displays 0 alert(a[1]()); // Displays 1 alert(a[2]()); // Displays 2 The results of this code are exactly what you would expect from a strict application of the lexical scoping rule: a function is executed in the scope in which it was defined. The reason that these results are surprising, however, is that you expect local scopes to cease to exist when the function that defines them exits. This is, in fact, what normally happens. When a function is invoked, a call object is created for it and placed on the scope chain. When the function exits, the call object is removed from the scope chain. When no nested functions are involved, the scope chain is the only reference to the call object. When the object is removed from the chain, there are no more references to it, and it ends up being garbage collected. But nested functions change the picture. If a nested function is created, the definition of that function refers to the call objects because that call object is the top of the scope chain in which the function was defined. If the nested function is used only within the outer function, however, the only reference to the nested function is in the call object. When the outer function returns, the nested function refers to the call object, and the call object refers to nested function, but there are no other references to either one, and so both objects become available for garbage collection. Things are different if you save a reference to the nested function in the global scope. You do so by using the nested function as the return value of the outer function or by storing the nested function as the property of some other object. In this case, there is an external reference to the nested function, and the nested function retains its reference to the call object of the outer function. The upshot is that the call object for that one particular invocation of the outer function continues to live, and the names and values of the function arguments and local variables persist in this object. JavaScript code cannot directly access the call object in any way, but the properties it defines are part of the scope chain for any invocation of the nested function. (Note that if an outer function stores global references to two nested functions, these two nested functions share the same call object, and changes made by an invocation of one function are visible to invocations of the other function.) JavaScript functions are a combination of code to be executed and the scope in which to execute them. This combination of code and scope is known as a closure in the computer science literature. All JavaScript functions are closures. These closures are only interesting, however, in the case discussed above: when a nested function is exported outside the scope in which it is defined. When a nested function is used in this way, it is often explicitly called a closure. Closures are an interesting and powerful technique. Although they are not commonly used in day-to-day JavaScript programming, it is still worth working to understand them. If you understand closures, you understand the scope chain and function call objects, and can truly call yourself an advanced JavaScript programmer. 8.8.4.1. Closure examplesOccasionally you'll want to write a function that can remember a value across invocations. The value cannot be stored in a local variable because the call object does not persist across invocations. A global variable would work, but that pollutes the global namespace. In Section 8.6.3., I presented a function named uniqueInteger() that used a property of itself to store the persistent value. You can use a closure to take this a step further and create a persistent and private variable. Here's how to write a function without a closure: // Return a different integer each time we're called uniqueID = function() { if (!arguments.callee.id) arguments.callee.id = 0; return arguments.callee.id++; }; The problem with this approach is that anyone can set uniqueID.id back to 0 and break the contract of the function that says that it never returns the same value twice. You can prevent that by storing the persistent value in a closure that only your function has access to: uniqueID = (function() { // The call object of this function holds our value var id = 0; // This is the private persistent value // The outer function returns a nested function that has access // to the persistent value. It is this nested function we're storing // in the variable uniqueID above. return function() { return id++; }; // Return and increment })(); // Invoke the outer function after defining it. Example 8-6 is a second closure example. It demonstrates that private persistent variables like the one used above can be shared by more than one function. Example 8-6. Private properties with closures
The most useful and least contrived example using closures that I am aware of is the breakpoint facility created by Steve Yen and published on http://trimpath.com as part of the TrimPath client-side framework. A breakpoint is a point in a function at which code execution stops and the programmer is given the opportunity to inspect the value of variables, evaluate expressions, call functions, and so on. Steve's breakpoint technique uses a closure to capture the current scope within a function (including local variables and function arguments) and combines this with the global eval() function to allow that scope to be inspected. eval() evaluates a string of JavaScript code and returns its result (you can read more about it in Part III). Here is a nested function that works as a self-inspecting closure: // Capture current scope and allow it to be inspected with eval() var inspector = function($) { return eval($); } This function uses the uncommon identifier $ as its argument name to reduce the possibility of naming conflicts with the scope it is intended to inspect. You can create a breakpoint within a function by passing this closure to a function like the one in Example 8-7. Example 8-7. Breakpoints using closures
Note that the inspect() function of Example 8-7 uses the Window.prompt() method to display text to the user and allow him to enter a string (see Window.prompt() in Part IV for further details). Here is a function for computing factorials that uses this breakpointing technique: function factorial(n) { // Create a closure for this function var inspector = function($) { return eval($); } inspect(inspector, "Entering factorial()"); var result = 1; while(n > 1) { result = result * n; n--; inspect(inspector, "factorial() loop"); } inspect(inspector, "Exiting factorial()"); return result; } 8.8.4.2. Closures and memory leaks in Internet ExplorerMicrosoft's Internet Explorer web browser uses a weak form of garbage collection for ActiveX objects and client-side DOM elements. These client-side objects are reference-counted and freed when their reference count reaches zero. This scheme fails when there are circular references, such as when a core JavaScript object refers to a document element and that document element has a property (such as an event handler) that refers back to the core JavaScript object. This kind of circular reference frequently occurs when closures are used with client-side programming in IE. When you use a closure, remember that the call object of the enclosing function, including all function arguments and local variables, will last as long as the closure does. If any of those function arguments or local variables refer to a client-side object, you may be creating a memory leak. A full discussion of this problem is beyond the scope of this book. See http://msdn.microsoft.com/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp for details. |