Effective JavaScript:编写高质量JavaScript代码的68个有效方法:
Item 11: Get Comfortable with Closures
Closures may be an unfamiliar concept to programmers coming from languages that don’t support them. And they may seem intimidating at first. But rest assured that making the effort to master closures will pay for itself many times over.
Luckily, there’s really nothing to be afraid of. Understanding closures only requires learning three essential facts. The first fact is that JavaScript allows you to refer to variables that were defined outside of the current function:
function makeSandwich() { var magicIngredient = "peanut butter"; function make(filling) { return magicIngredient + " and " + filling; } return make("jelly"); } makeSandwich(); // "peanut butter and jelly"
Notice how the inner make function refers to magicIngredient, a variable defined in the outer makeSandwich function.
The second fact is that functions can refer to variables defined in outer functions even after those outer functions have returned! If that sounds implausible, remember that JavaScript functions are firstclass objects (see Item 19). That means that you can return an inner function to be called sometime later on:
function sandwichMaker() { var magicIngredient = "peanut butter"; function make(filling) { return magicIngredient + " and " + filling; } return make; } var f = sandwichMaker(); f("jelly"); // "peanut butter and jelly" f("bananas"); // "peanut butter and bananas" f("marshmallows"); // "peanut butter and marshmallows"
This is almost identical to the first example, except that instead of immediately calling make("jelly") inside the outer function, sandwichMaker returns the make function itself. So the value of f is the inner make function, and calling f effectively calls make. But somehow, even though sandwichMaker already returned, make remembers the value of magicIngredient.
How does this work? The answer is that JavaScript function values contain more information than just the code required to execute when they’re called. They also internally store any variables they may refer to that are defined in their enclosing scopes. Functions that keep track of variables from their containing scopes are known as closures. The make function is a closure whose code refers to two outer variables: magicIngredient and filling. Whenever the make function is called, its code is able to refer to these two variables because they are stored in the closure.
A function can refer to any variables in its scope, including the parameters and variables of outer functions. We can use this to make a more general-purpose sandwichMaker:
function sandwichMaker(magicIngredient) { function make(filling) { return magicIngredient + " and " + filling; } return make; } var hamAnd = sandwichMaker("ham"); hamAnd("cheese"); // "ham and cheese" hamAnd("mustard"); // "ham and mustard" var turkeyAnd = sandwichMaker("turkey"); turkeyAnd("Swiss"); // "turkey and Swiss" turkeyAnd("Provolone"); // "turkey and Provolone"
This example creates two distinct functions, hamAnd and turkeyAnd. Even though they both come from the same make definition, they are two distinct objects: The first function stores "ham" as the value of magicIngredient, and the second stores "turkey".
Closures are one of JavaScript’s most elegant and expressive features, and are at the heart of many useful idioms. JavaScript even provides a more convenient literal syntax for constructing closures, the function expression:
function sandwichMaker(magicIngredient) { return function(filling) { return magicIngredient + " and " + filling; }; }
Notice that this function expression is anonymous: It’s not even necessary to name the function since we are only evaluating it to produce a new function value, but do not intend to call it locally. Function expressions can have names as well (see Item 14).
The third and final fact to learn about closures is that they can update the values of outer variables. Closures actually store references to their outer variables, rather than copying their values. So updates are visible to any closures that have access to them. A simple idiom that illustrates this is a box—an object that stores an internal value that can be read and updated:
function box() { var val = undefined; return { set: function(newVal) { val = newVal; }, get: function() { return val; }, type: function() { return typeof val; } }; } var b = box(); b.type(); // "undefined" b.set(98.6); b.get(); // 98.6 b.type(); // "number"
This example produces an object containing three closures: its set, get, and type properties. Each of these closures shares access to the val variable. The set closure updates the value of val, and subsequently calling get and type sees the results of the update.
Things to Remember
✦ Functions can refer to variables defined in outer scopes.
✦ Closures can outlive the function that creates them.
✦ Closures internally store references to their outer variables, and can both read and update their stored variables.