JavaScript: The Definitive Guide, Sixth Editio javaScript权威指南(第6版) pdf 文字版-文字版, javascript电子书, 和javascript 有关的电子书:

8.6 Closures

8.6 Closures


Like most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the current scope chain. (Before reading the rest of this section, you may want to review the material on variable scope and the scope chain in §3.10 and §3.10.3 .) This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.4

Technically, all JavaScript functions are closures: they are objects, and they have a scope chain associated with them. Most functions are invoked using the same scope chain that was in effect when the function was defined, and it doesn’t really matter that there is a closure involved. Closures become interesting when they are invoked under a

4. This is an old term that refers to the fact that the function’s variables have bindings in the scope chain and that therefore the function is “closed over” its variables.

different scope chain than the one that was in effect when they were defined. This happens most commonly when a nested function object is returned from the function within which it was defined. There are a number of powerful programming techniques that involve this kind of nested function closures, and their use has become relatively common in JavaScript programming. Closures may seem confusing when you first encounter them, but it is important that you understand them well enough to use them comfortably.

The first step to understanding closures is to review the lexical scoping rules for nested functions. Consider the following code (which is similar to code you’ve already seen in §3.10 ):

var scope = "global scope"; // A global variable

function checkscope() {

var scope = "local scope"; // A local variable

function f() { return scope; } // Return the value in scope here

return f();

}

checkscope() // => "local scope"

The checkscope() function declares a local variable and then defines and invokes a function that returns the value of that variable. It should be clear to you why the call to checkscope() returns “local scope”. Now let’s change the code just slightly. Can you tell what this code will return?

var scope = "global scope"; // A global variable

function checkscope() {

var scope = "local scope"; // A local variable

function f() { return scope; } // Return the value in scope here

return f;

}

checkscope()() // What does this return?

In this code, a pair of parentheses has moved from inside checkscope() to outside of it. Instead of invoking the nested function and returning its result, checkscope() now just returns the nested function object itself. What happens when we invoke that nested function (with the second pair of parentheses in the last line of code) outside of the function in which it was defined?

Remember the fundamental rule of lexical scoping: JavaScript functions are executed using the scope chain that was in effect when they were defined. The nested function f()was defined under a scope chain in which the variable scopewas bound to the value “local scope”. That binding is still in effect when f is executed, wherever it is executed from. So the last line of code above returns “local scope”, not “global scope”. This, in a nutshell, is the surprising and powerful nature of closures: they capture the local variable (and parameter) bindings of the outer function within which they are defined.

8.6 Closures | 181

Implementing Closures

Closures are easy to understand if you simply accept the lexical scoping rule: functions are executed using the scope chain that was in effect when they were defined. Some programmers find closures confusing, however, because they get caught up in implementation details. Surely, they think, the local variables defined in the outer function cease to exist when the outer function returns, so how can the nested function execute using a scope chain that does not exist anymore? If you’re wondering about this yourself, then you have probably been exposed to low-level programming languages like C and to stack-based CPU architectures: if a function’s local variables are defined on a CPU stack, then they would indeed cease to exist when the function returned.

But remember our definition of scope chain from §3.10.3 . We described it as a list of objects, not a stack of bindings. Each time a JavaScript function is invoked, a new object is created to hold the local variables for that invocation, and that object is added to the scope chain. When the function returns, that variable binding object is removed from the scope chain. If there were no nested functions, there are no more references to the binding object and it gets garbage collected. If there were nested functions defined, then each of those functions has a reference to the scope chain, and that scope chain refers to the variable binding object. If those nested functions objects remained within their outer function, however, then they themselves will be garbage collected, along with the variable binding object they referred to. But if the function defines a nested function and returns it or stores it into a property somewhere, then there will be an external reference to the nested function. It won’t be garbage collected, and the variable binding object it refers to won’t be garbage collected either.

In §8.4.1 we defined a uniqueInteger() function that used a property of the function itself to keep track of the next value to be returned. A shortcoming of that approach is that buggy or malicious code could reset the counter or set it to a noninteger, causing the uniqueInteger() function to violate the “unique” or the “integer” part of its contract. Closures capture the local variables of a single function invocation and can use those variables as private state. Here is how we could rewrite the uniqueInteger() function using closures:

var uniqueInteger = (function() { // Define and invoke var counter = 0; // Private state of function below return function() { return counter++; };

}());

In order to understand this code, you have to read it carefully. At first glance, the first line of code looks like it is assigning a function to the variable uniqueInteger. In fact, the code is defining and invoking (as hinted by the open parenthesis on the first line) a function, so it is the return value of the function that is being assigned to uniqueInteger. Now, if we study the body of the function, we see that its return value is another function. It is this nested function object that gets assigned to uniqueInteger. The nested function has access to the variables in scope, and can use the counter variable defined in the outer function. Once that outer function returns, no other code can see the countervariable: the inner function has exclusive access to it.

Private variables like counter need not be exclusive to a single closure: it is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope chain. Consider the following code:

function counter() { var n = 0; return {

count: function() { return n++; }, reset: function() { n = 0; } }; }

var c = counter(), d = counter(); // Create two counters c.count() // => 0 d.count() // => 0: they count independently c.reset() // reset() and count() methods share state c.count() // => 0: because we reset c d.count() // => 1: d was not reset

The counter() function returns a “counter” object. This object has two methods: count() returns the next integer, and reset() resets the internal state. The first thing to understand is that the two methods share access to the private variable n. The second thing to understand is that each invocation of counter() creates a new scope chain and a new private variable. So if you call counter() twice, you get two counter objects with different private variables. Calling count() or reset() on one counter object has no effect on the other.

It is worth noting here that you can combine this closure technique with property getters and setters. The following version of the counter() function is a variation on code that appeared in §6.6 , but it uses closures for private state rather than relying on a regular object property:

function counter(n) { // Function argument n is the private variable

return { // Property getter method returns and increments private counter var. get count() { return n++; }, // Property setter doesn't allow the value of n to decrease set count(m) { if (m >= n) n = m; else throw Error("count can only be set to a larger value"); }

}; }

var c = counter(1000); c.count // => 1000 c.count // => 1001 c.count = 2000 c.count // => 2000 c.count = 2000 // => Error!

8.6 Closures | 183

Note that this version of the counter() function does not declare a local variable, but just uses its parameter n to hold the private state shared by the property accessor methods. This allows the caller of counter()to specify the initial value of the private variable.

Example 8-4 is a generalization of the shared private state through closures technique we’ve been demonstrating here. This example defines an addPrivateProperty() function that defines a private variable and two nested functions to get and set the value of that variable. It adds these nested functions as methods of the object you specify:

Example 8-4. Private property accessor methods using closures

// This function adds property accessor methods for a property with // the specified name to the object o. The methods are named get// and set. If a predicate function is supplied, the setter // method uses it to test its argument for validity before storing it. // If the predicate returns false, the setter method throws an exception. // // The unusual thing about this function is that the property value // that is manipulated by the getter and setter methods is not stored in // the object o. Instead, the value is stored only in a local variable // in this function. The getter and setter methods are also defined // locally to this function and therefore have access to this local variable. // This means that the value is private to the two accessor methods, and it // cannot be set or modified except through the setter method. function addPrivateProperty(o, name, predicate) {

var value; // This is the property value

// The getter method simply returns the value. o["get" + name] = function() { return value; };

// The setter method stores the value or throws an exception if // the predicate rejects the value. o["set" + name] = function(v) {

if (predicate && !predicate(v)) throw Error("set" + name + ": invalid value " + v); else value = v; }; }

// The following code demonstrates the addPrivateProperty() method. var o = {}; // Here is an empty object

// Add property accessor methods getName and setName() // Ensure that only string values are allowed addPrivateProperty(o, "Name", function(x) { return typeof x == "string"; });

o.setName("Frank"); // Set the property value console.log(o.getName()); // Get the property value o.setName(0); // Try to set a value of the wrong type

We’ve now seen a number of examples in which two closures are defined in the same scope chain and share access to the same private variable or variables. This is an important technique, but it is just as important to recognize when closures inadvertently share access to a variable that they should not share. Consider the following code:

// This function returns a function that always returns v function constfunc(v) { return function() { return v; }; }

// Create an array of constant functions: var funcs = []; for(var i = 0; i < 10; i++) funcs[i] = constfunc(i);

// The function at array element 5 returns the value 5. funcs[5]() // => 5

When working with code like this that creates multiple closures using a loop, it is a common error to try to move the loop within the function that defines the closures. Think about the following code, for example:

// Return an array of functions that return the values 0-9

function constfuncs() {

var funcs = [];

for(var i = 0; i < 10; i++)

funcs[i] = function() { return i; };

return funcs;

}

var funcs = constfuncs(); funcs[5]() // What does this return?

The code above creates 10 closures, and stores them in an array. The closures are all defined within the same invocation of the function, so they share access to the variable

i. When constfuncs()returns, the value of the variable iis 10, and all 10 closures share this value. Therefore, all the functions in the returned array of functions return the same value, which is not what we wanted at all. It is important to remember that the scope chain associated with a closure is “live.” Nested functions do not make private copies of the scope or make static snapshots of the variable bindings.

Another thing to remember when writing closures is that this is a JavaScript keyword, not a variable. As discussed earlier, every function invocation has a this value, and a closure cannot access the this value of its outer function unless the outer function has saved that value into a variable:

var self = this; // Save this value in a variable for use by nested funcs.

The argumentsbinding is similar. This is not a language keyword, but it is automatically declared for every function invocation. Since a closure has its own binding for arguments, it cannot access the outer function’s arguments array unless the outer function has saved that array into a variable by a different name:

var outerArguments = arguments; // Save for use by nested functions

Example 8-5 , later in this chapter, defines a closure that uses these techniques to refer to both the this and arguments values of the outer function.

8.6 Closures | 185

欢迎转载,转载请注明来自一手册:http://yishouce.com/book/1/27668.html
友情链接It题库(ittiku.com)| 版权归yishouce.com所有| 友链等可联系 admin#yishouce.com|粤ICP备16001685号-1