可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question's answers are a community effort. Edit existing answers to improve this post. It is not currently accepting new answers or interactions.
How would you explain JavaScript closures to someone with a knowledge of the concepts they consist of (for example functions, variables and the like), but does not understand closures themselves?
I have seen the Scheme example given on Wikipedia, but unfortunately it did not help.
回答1:
JavaScript closures for beginners
Submitted by Morris on Tue, 2006-02-21 10:19. Community-edited since.
Closures are not magic
This page explains closures so that a programmer can understand them — using working JavaScript code. It is not for gurus or functional programmers.
Closures are not hard to understand once the core concept is grokked. However, they are impossible to understand by reading any theoretical or academically oriented explanations!
This article is intended for programmers with some programming experience in a mainstream language, and who can read the following JavaScript function:
function sayHello(name) {
var text = 'Hello ' + name;
var say = function() { console.log(text); }
say();
}
sayHello('Joe');
Two brief summaries
When a function (foo
) declares other functions (bar and baz), the family of local variables created in foo
is not destroyed when the function exits. The variables merely become invisible to the outside world. foo
can therefore cunningly return the functions bar
and baz
, and they can continue to read, write and communicate with each other through this closed-off family of variables ("the closure") that nobody else can meddle with, not even someone who calls foo
again in future.
A closure is one way of supporting first-class functions; it is an expression that can reference variables within its scope (when it was first declared), be assigned to a variable, be passed as an argument to a function, or be returned as a function result.
An example of a closure
The following code returns a reference to a function:
function sayHello2(name) {
var text = 'Hello ' + name; // Local variable
var say = function() { console.log(text); }
return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"
Most JavaScript programmers will understand how a reference to a function is returned to a variable (say2
) in the above code. If you don't, then you need to look at that before you can learn closures. A programmer using C would think of the function as returning a pointer to a function, and that the variables say
and say2
were each a pointer to a function.
There is a critical difference between a C pointer to a function and a JavaScript reference to a function. In JavaScript, you can think of a function reference variable as having both a pointer to a function as well as a hidden pointer to a closure.
The above code has a closure because the anonymous function function() { console.log(text); }
is declared inside another function, sayHello2()
in this example. In JavaScript, if you use the function
keyword inside another function, you are creating a closure.
In C and most other common languages, after a function returns, all the local variables are no longer accessible because the stack-frame is destroyed.
In JavaScript, if you declare a function within another function, then the local variables of the outer function can remain accessible after returning from it. This is demonstrated above, because we call the function say2()
after we have returned from sayHello2()
. Notice that the code that we call references the variable text
, which was a local variable of the function sayHello2()
.
function() { console.log(text); } // Output of say2.toString();
Looking at the output of say2.toString()
, we can see that the code refers to the variable text
. The anonymous function can reference text
which holds the value 'Hello Bob'
because the local variables of sayHello2()
have been secretly kept alive in a closure.
The genius is that in JavaScript a function reference also has a secret reference to the closure it was created in — similar to how delegates are a method pointer plus a secret reference to an object.
More examples
For some reason, closures seem really hard to understand when you read about them, but when you see some examples, it becomes clear how they work (it took me a while).
I recommend working through the examples carefully until you understand how they work. If you start using closures without fully understanding how they work, you would soon create some very weird bugs!
Example 3
This example shows that the local variables are not copied — they are kept by reference. It is as though the stack-frame stays alive in memory even after the outer function exits!
function say667() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = say667();
sayNumber(); // logs 43
Example 4
All three global functions have a common reference to the same closure because they are all declared within a single call to setupSomeGlobals()
.
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 42
oldLog() // 5
The three functions have shared access to the same closure — the local variables of setupSomeGlobals()
when the three functions were defined.
Note that in the above example, if you call setupSomeGlobals()
again, then a new closure (stack-frame!) is created. The old gLogNumber
, gIncreaseNumber
, gSetNumber
variables are overwritten with new functions that have the new closure. (In JavaScript, whenever you declare a function inside another function, the inside function(s) is/are recreated again each time the outside function is called.)
Example 5
This example shows that the closure contains any local variables that were declared inside the outer function before it exited. Note that the variable alice
is actually declared after the anonymous function. The anonymous function is declared first and when that function is called it can access the alice
variable because alice
is in the same scope (JavaScript does variable hoisting).
Also sayAlice()()
just directly calls the function reference returned from sayAlice()
— it is exactly the same as what was done previously but without the temporary variable.
function sayAlice() {
var say = function() { console.log(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return say;
}
sayAlice()();// logs "Hello Alice"
Tricky: note the say
variable is also inside the closure and could be accessed by any other function that might be declared within sayAlice()
, or it could be accessed recursively within the inside function.
Example 6
This one is a real gotcha for many people, so you need to understand it. Be very careful if you are defining a function within a loop: the local variables from the closure may not act as you might first think.
You need to understand the "variable hoisting" feature in Javascript in order to understand this example.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() //logs "item2 undefined" 3 times
The line result.push( function() {console.log(item + ' ' + list[i])}
adds a reference to an anonymous function three times to the result array. If you are not so familiar with anonymous functions think of it like:
pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);
Note that when you run the example, "item2 undefined"
is logged three times! This is because just like previous examples, there is only one closure for the local variables for buildList
(which are result
, i
, list
and item
). When the anonymous functions are called on the line fnlist[j]()
; they all use the same single closure, and they use the current value for i
and item
within that one closure (where i
has a value of 3
because the loop had completed, and item
has a value of 'item2'
). Note we are indexing from 0 hence item
has a value of item2
. And the i++ will increment i
to the value 3
.
It may be helpful to see what happens when a block-level declaration of the variable item
is used (via the let
keyword) instead of a function-scoped variable declaration via the var
keyword. If that change is made, then each anonymous function in the array result
has its own closure; when the example is run the output is as follows:
item0 undefined
item1 undefined
item2 undefined
If the variable i
is also defined using let
instead of var
, then the output is:
item0 1
item1 2
item2 3
Example 7
In this final example, each call to the main function creates a separate closure.
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
console.log('num: ' + num +
'; anArray: ' + anArray.toString() +
'; ref.someVar: ' + ref.someVar + ';');
}
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj); // attention here: new closure assigned to a new variable!
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;
Summary
If everything seems completely unclear, then the best thing to do is to play with the examples. Reading an explanation is much harder than understanding examples.
My explanations of closures and stack-frames, etc. are not technically correct — they are gross simplifications intended to help to understand. Once the basic idea is grokked, you can pick up the details later.
Final points:
- Whenever you use
function
inside another function, a closure is used.
- Whenever you use
eval()
inside a function, a closure is used. The text you eval
can reference local variables of the function, and within eval
you can even create new local variables by using eval('var foo = …')
- When you use
new Function(…)
(the Function constructor) inside a function, it does not create a closure. (The new function cannot reference the local variables of the outer function.)
- A closure in JavaScript is like keeping a copy of all the local variables, just as they were when a function exited.
- It is probably best to think that a closure is always created just an entry to a function, and the local variables are added to that closure.
- A new set of local variables is kept every time a function with a closure is called (given that the function contains a function declaration inside it, and a reference to that inside function is either returned or an external reference is kept for it in some way).
- Two functions might look like they have the same source text, but have completely different behavior because of their 'hidden' closure. I don't think JavaScript code can actually find out if a function reference has a closure or not.
- If you are trying to do any dynamic source code modifications (for example:
myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));
), it won't work if myFunction
is a closure (of course, you would never even think of doing source code string substitution at runtime, but...).
- It is possible to get function declarations within function declarations within functions… and you can get closures at more than one level.
- I think normally a closure is a term for both the function along with the variables that are captured. Note that I do not use that definition in this article!
- I suspect that closures in JavaScript differ from those normally found in functional languages.
Links
- Douglas Crockford's simulated private attributes and private methods for an object, using closures.
- A great explanation of how closures can cause memory leaks in IE if you are not careful.
Thanks
If you have just learned closures (here or elsewhere!), then I am interested in any feedback from you about any changes you might suggest that could make this article clearer. Send an email to morrisjohns.com (morris_closure @). Please note that I am not a guru on JavaScript — nor on closures.
Original post by Morris can be found in the Internet Archive.
回答2:
Whenever you see the function keyword within another function, the inner function has access to variables in the outer function.
function foo(x) {
var tmp = 3;
function bar(y) {
console.log(x + y + (++tmp)); // will log 16
}
bar(10);
}
foo(2);
This will always log 16, because bar
can access the x
which was defined as an argument to foo
, and it can also access tmp
from foo
.
That is a closure. A function doesn't have to return in order to be called a closure. Simply accessing variables outside of your immediate lexical scope creates a closure.
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + (++tmp)); // will also log 16
}
}
var bar = foo(2); // bar is now a closure.
bar(10);
The above function will also log 16, because bar
can still refer to x
and tmp
, even though it is no longer directly inside the scope.
However, since tmp
is still hanging around inside bar
's closure, it is also being incremented. It will be incremented each time you call bar
.
The simplest example of a closure is this:
var a = 10;
function test() {
console.log(a); // will output 10
console.log(b); // will output 6
}
var b = 6;
test();
When a JavaScript function is invoked, a new execution context is created. Together with the function arguments and the parent object, this execution context also receives all the variables declared outside of it (in the above example, both 'a' and 'b').
It is possible to create more than one closure function, either by returning a list of them or by setting them to global variables. All of these will refer to the same x
and the same tmp
, they don't make their own copies.
Here the number x
is a literal number. As with other literals in JavaScript, when foo
is called, the number x
is copied into foo
as its argument x
.
On the other hand, JavaScript always uses references when dealing with objects. If say, you called foo
with an object, the closure it returns will reference that original object!
function foo(x) {
var tmp = 3;
return function (y) {
console.log(x + y + tmp);
x.memb = x.memb ? x.memb + 1 : 1;
console.log(x.memb);
}
}
var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);
As expected, each call to bar(10)
will increment x.memb
. What might not be expected, is that x
is simply referring to the same object as the age
variable! After a couple of calls to bar
, age.memb
will be 2! This referencing is the basis for memory leaks with HTML objects.
回答3:
FOREWORD: this answer was written when the question was:
Like the old Albert said : "If you can't explain it to a six-year old, you really don't understand it yourself.”. Well I tried to explain JS closures to a 27 years old friend and completely failed.
Can anybody consider that I am 6 and strangely interested in that subject ?
I'm pretty sure I was one of the only people that attempted to take the initial question literally. Since then, the question has mutated several times, so my answer may now seem incredibly silly & out of place. Hopefully the general idea of the story remains fun for some.
I'm a big fan of analogy and metaphor when explaining difficult concepts, so let me try my hand with a story.
Once upon a time:
There was a princess...
function princess() {
She lived in a wonderful world full of adventures. She met her Prince Charming, rode around her world on a unicorn, battled dragons, encountered talking animals, and many other fantastical things.
var adventures = [];
function princeCharming() { /* ... */ }
var unicorn = { /* ... */ },
dragons = [ /* ... */ ],
squirrel = "Hello!";
/* ... */
But she would always have to return back to her dull world of chores and grown-ups.
return {
And she would often tell them of her latest amazing adventure as a princess.
story: function() {
return adventures[adventures.length - 1];
}
};
}
But all they would see is a little girl...
var littleGirl = princess();
...telling stories about magic and fantasy.
littleGirl.story();
And even though the grown-ups knew of real princesses, they would never believe in the unicorns or dragons because they could never see them. The grown-ups said that they only existed inside the little girl's imagination.
But we know the real truth; that the little girl with the princess inside...
...is really a princess with a little girl inside.
回答4:
Taking the question seriously, we should find out what a typical 6-year-old is capable of cognitively, though admittedly, one who is interested in JavaScript is not so typical.
On Childhood Development: 5 to 7 Years it says:
Your child will be able to follow two-step directions. For example, if you say to your child, "Go to the kitchen and get me a trash bag" they will be able to remember that direction.
We can use this example to explain closures, as follows:
The kitchen is a closure that has a local variable, called trashBags
. There is a function inside the kitchen called getTrashBag
that gets one trash bag and returns it.
We can code this in JavaScript like this:
function makeKitchen() {
var trashBags = ['A', 'B', 'C']; // only 3 at first
return {
getTrashBag: function() {
return trashBags.pop();
}
};
}
var kitchen = makeKitchen();
console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A
Further points that explain why closures are interesting:
- Each time
makeKitchen()
is called, a new closure is created with its own separate trashBags
.
- The
trashBags
variable is local to the inside of each kitchen and is not accessible outside, but the inner function on the getTrashBag
property does have access to it.
- Every function call creates a closure, but there would be no need to keep the closure around unless an inner function, which has access to the inside of the closure, can be called from outside the closure. Returning the object with the
getTrashBag
function does that here.
回答5:
The Straw Man
I need to know how many times a button has been clicked and do something on every third click...
Fairly Obvious Solution
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');
element.addEventListener("click", function() {
// Increment outside counter
counter++;
if (counter === 3) {
// Do something every third time
console.log("Third time's the charm!");
// Reset counter
counter = 0;
}
});
<button id="button">Click Me!</button>
Now this will work, but it does encroach into the outer scope by adding a variable, whose sole purpose is to keep track of the count. In some situations, this would be preferable as your outer application might need access to this information. But in this case, we are only changing every third click's behavior, so it is preferable to enclose this functionality inside the event handler.
Consider this option
var element = document.getElementById('button');
element.addEventListener("click", (function() {
// init the count to 0
var count = 0;
return function(e) { // <- This function becomes the click handler
count++; // and will retain access to the above `count`
if (count === 3) {
// Do something every third time
console.log("Third time's the charm!");
//Reset counter
count = 0;
}
};
})());
<button id="button">Click Me!</button>
Notice a few things here.
In the above example, I am using the closure behavior of JavaScript. This behavior allows any function to have access to the scope in which it was created, indefinitely. To practically apply this, I immediately invoke a function that returns another function, and because the function I'm returning has access to the internal count variable (because of the closure behavior explained above) this results in a private scope for usage by the resulting function... Not so simple? Let's dilute it down...
A simple one-line closure
// _______________________Immediately invoked______________________
// | |
// | Scope retained for use ___Returned as the____ |
// | only by returned function | value of func | |
// | | | | | |
// v v v v v v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();
All variables outside the returned function are available to the returned function, but they are not directly available to the returned function object...
func(); // Alerts "val"
func.a; // Undefined
Get it? So in our primary example, the count variable is contained within the closure and always available to the event handler, so it retains its state from click to click.
Also, this private variable state is fully accessible, for both readings and assigning to its private scoped variables.
There you go; you're now fully encapsulating this behavior.
Full Blog Post (including jQuery considerations)
回答6:
Closures are hard to explain because they are used to make some behaviour work that everybody intuitively expects to work anyway. I find the best way to explain them (and the way that I learned what they do) is to imagine the situation without them:
var bind = function(x) {
return function(y) { return x + y; };
}
var plus5 = bind(5);
console.log(plus5(3));
What would happen here if JavaScript didn't know closures? Just replace the call in the last line by its method body (which is basically what function calls do) and you get:
console.log(x + 3);
Now, where's the definition of x
? We didn't define it in the current scope. The only solution is to let plus5
carry its scope (or rather, its parent's scope) around. This way, x
is well-defined and it is bound to the value 5.
回答7:
A closure is much like an object. It gets instantiated whenever you call a function.
The scope of a closure in JavaScript is lexical, which means that everything that is contained within the function the closure belongs to, has access to any variable that is in it.
A variable is contained in the closure if you
- assign it with
var foo=1;
or
- just write
var foo;
If an inner function (a function contained inside another function) accesses such a variable without defining it in its own scope with var, it modifies the content of the variable in the outer closure.
A closure outlives the runtime of the function that spawned it. If other functions make it out of the closure/scope in which they are defined (for instance as return values), those will continue to reference that closure.
Example
function example(closure) {
// define somevariable to live in the closure of example
var somevariable = 'unchanged';
return {
change_to: function(value) {
somevariable = value;
},
log: function(value) {
console.log('somevariable of closure %s is: %s',
closure, somevariable);
}
}
}
closure_one = example('one');
closure_two = example('two');
closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();
Output
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
回答8:
OK, 6-year-old closures fan. Do you want to hear the simplest example of closure?
Let's imagine the next situation: a driver is sitting in a car. That car is inside a plane. Plane is in the airport. The ability of driver to access things outside his car, but inside the plane, even if that plane leaves an airport, is a closure. That's it. When you turn 27, look at the more detailed explanation or at the example below.
Here is how I can convert my plane story into the code.
var plane = function(defaultAirport) {
var lastAirportLeft = defaultAirport;
var car = {
driver: {
startAccessPlaneInfo: function() {
setInterval(function() {
console.log("Last airport was " + lastAirportLeft);
}, 2000);
}
}
};
car.driver.startAccessPlaneInfo();
return {
leaveTheAirport: function(airPortName) {
lastAirportLeft = airPortName;
}
}
}("Boryspil International Airport");
plane.leaveTheAirport("John F. Kennedy");
回答9:
This is an attempt to clear up several (possible) misunderstandings about closures that appear in some of the other answers.
- A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all in order for its closure to be created. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be called immediately or any time later. Therefore, the closure of the enclosing function is probably created as soon as the enclosing function is called since any inner function has access to that closure whenever the inner function is called, before or after the enclosing function returns.
- A closure does not reference a copy of the old values of variables in its scope. The variables themselves are part of the closure, and so the value seen when accessing one of those variables is the latest value at the time it is accessed. This is why inner functions created inside of loops can be tricky, since each one has access to the same outer variables rather than grabbing a copy of the variables at the time the function is created or called.
- The "variables" in a closure include any named functions declared within the function. They also include arguments of the function. A closure also has access to its containing closure's variables, all the way up to the global scope.
- Closures use memory, but they don't cause memory leaks since JavaScript by itself cleans up its own circular structures that are not referenced. Internet Explorer memory leaks involving closures are created when it fails to disconnect DOM attribute values that reference closures, thus maintaining references to possibly circular structures.
回答10:
I wrote a blog post a while back explaining closures. Here's what I said about closures in terms of why you'd want one.
Closures are a way to let a function
have persistent, private variables -
that is, variables that only one
function knows about, where it can
keep track of info from previous times
that it was run.
In that sense, they let a function act a bit like an object with private attributes.
Full post:
So what are these closure thingys?
回答11:
Closures are simple:
The following simple example covers all the main points of JavaScript closures.*
Here is a factory that produces calculators that can add and multiply:
function make_calculator() {
var n = 0; // this calculator stores a single number n
return {
add: function(a) {
n += a;
return n;
},
multiply: function(a) {
n *= a;
return n;
}
};
}
first_calculator = make_calculator();
second_calculator = make_calculator();
first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000
The key point: Each call to make_calculator
creates a new local variable n
, which continues to be usable by that calculator's add
and multiply
functions long after make_calculator
returns.
If you are familiar with stack frames, these calculators seem strange: How can they keep accessing n
after make_calculator
returns? The answer is to imagine that JavaScript doesn't use "stack frames", but instead uses "heap frames", which can persist after the function call that made them returns.
Inner functions like add
and multiply
, which access variables declared in an outer function**, are called closures.
That is pretty much all there is to closures.
* For example, it covers all the points in the "Closures for Dummies" article given in another answer, except example 6, which simply shows that variables can be used before they are declared, a nice fact to know but completely unrelated to closures. It also covers all the points in the accepted answer, except for the points (1) that functions copy their arguments into local variables (the named function arguments), and (2) that copying numbers creates a new number, but copying an object reference gives you another reference to the same object. These are also good to know but again completely unrelated to closures. It is also very similar to the example in this answer but a bit shorter and less abstract. It does not cover the point of this answer or this comment, which is that JavaScript makes it difficult to plug the current value of a loop variable into your inner function: The "plugging in" step can only be done with a helper function that encloses your inner function and is invoked on each loop iteration. (Strictly speaking, the inner function accesses the helper function's copy of the variable, rather than having anything plugged in.) Again, very useful when creating closures, but not part of what a closure is or how it works. There is additional confusion due to closures working differently in functional languages like ML, where variables are bound to values rather than to storage space, providing a constant stream of people who understand closures in a way (namely the "plugging in" way) that is simply incorrect for JavaScript, where variables are always bound to storage space, and never to values.
** Any outer function, if several are nested, or even in the global context, as this answer points out clearly.
回答12:
How I'd explain it to a six-year-old:
You know how grown-ups can own a house, and they call it home? When a mom has a child, the child doesn't really own anything, right? But its parents own a house, so whenever someone asks the child "Where's your home?", he/she can answer "that house!", and point to the house of its parents. A "Closure" is the ability of the child to always (even if abroad) be able to say it has a home, even though it's really the parent's who own the house.
回答13:
Can you explain closures to a 5-year-old?*
I still think Google's explanation works very well and is concise:
/*
* When a function is defined in another function and it
* has access to the outer function's context even after
* the outer function returns.
*
* An important concept to learn in JavaScript.
*/
function outerFunction(someNum) {
var someString = 'Hey!';
var content = document.getElementById('content');
function innerFunction() {
content.innerHTML = someNum + ': ' + someString;
content = null; // Internet Explorer memory leak for DOM reference
}
innerFunction();
}
outerFunction(1);
*A C# question
回答14:
I tend to learn better by GOOD/BAD comparisons. I like to see working code followed by non-working code that someone is likely to encounter. I put together a jsFiddle that does a comparison and tries to boil down the differences to the simplest explanations I could come up with.
Closures done right:
console.log('CLOSURES DONE RIGHT');
var arr = [];
function createClosure(n) {
return function () {
return 'n = ' + n;
}
}
for (var index = 0; index < 10; index++) {
arr[index] = createClosure(index);
}
for (var index in arr) {
console.log(arr[index]());
}
In the above code createClosure(n)
is invoked in every iteration of the loop. Note that I named the variable n
to highlight that it is a new variable created in a new function scope and is not the same variable as index
which is bound to the outer scope.
This creates a new scope and n
is bound to that scope; this means we have 10 separate scopes, one for each iteration.
createClosure(n)
returns a function that returns the n within that scope.
Within each scope n
is bound to whatever value it had when createClosure(n)
was invoked so the nested function that gets returned will always return the value of n
that it had when createClosure(n)
was invoked.
Closures done wrong:
console.log('CLOSURES DONE WRONG');
function createClosureArray() {
var badArr = [];
for (var index = 0; index < 10; index++) {
badArr[index] = function () {
return 'n = ' + index;
};
}
return badArr;
}
var badArr = createClosureArray();
for (var index in badArr) {
console.log(badArr[index]());
}
In the above code the loop was moved within the createClosureArray()
function and the function now just returns the completed array, which at first glance seems more intuitive.
What might not be obvious is that since createClosureArray()
is only invoked once only one scope is created for this function instead of one for every iteration of the loop.
Within this function a variable named index
is defined. The loop runs and adds functions to the array that return index
. Note that index
is defined within the createClosureArray
function which only ever gets invoked one time.
Because there was only one scope within the createClosureArray()
function, index
is only bound to a value within that scope. In other words, each time the loop changes the value of index
, it changes it for everything that references it within that scope.
All of the functions added to the array return the SAME index
variable from the parent scope where it was defined instead of 10 different ones from 10 different scopes like the first example. The end result is that all 10 functions return the same variable from the same scope.
After the loop finished and index
was done being modified the end value was 10, therefore every function added to the array returns the value of the single index
variable which is now set to 10.
Result
CLOSURES DONE RIGHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9
CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
回答15:
Wikipedia on closures:
In computer science, a closure is a function together with a referencing environment for the nonlocal names (free variables) of that function.
Technically, in JavaScript, every function is a closure. It always has an access to variables defined in the surrounding scope.
Since scope-defining construction in JavaScript is a function, not a code block like in many other languages, what we usually mean by closure in JavaScript is a function working with nonlocal variables defined in already executed surrounding function.
Closures are often used for creating functions with some hidden private data (but it's not always the case).
var db = (function() {
// Create a hidden object, which will hold the data
// it's inaccessible from the outside.
var data = {};
// Make a function, which will provide some access to the data.
return function(key, val) {
if (val === undefined) { return data[key] } // Get
else { return data[key] = val } // Set
}
// We are calling the anonymous surrounding function,
// returning the above inner function, which is a closure.
})();
db('x') // -> undefined
db('x', 1) // Set x to 1
db('x') // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.
ems
The example above is using an anonymous function, which was executed once. But it does not have to be. It can be named (e.g. mkdb
) and executed later, generating a database function each time it is invoked. Every generated function will have its own hidden database object. Another usage example of closures is when we don't return a function, but an object containing multiple functions for different purposes, each of those function having access to the same data.
回答16:
I put together an interactive JavaScript tutorial to explain how closures work.
What's a Closure?
Here's one of the examples:
var create = function (x) {
var f = function () {
return x; // We can refer to x here!
};
return f;
};
// 'create' takes one argument, creates a function
var g = create(42);
// g is a function that takes no arguments now
var y = g();
// y is 42 here
回答17:
The children will always remember the secrets they have shared with their parents, even after their parents are
gone. This is what closures are for functions.
The secrets for JavaScript functions are the private variables
var parent = function() {
var name = "Mary"; // secret
}
Every time you call it, local variable "name" is created and given name "Mary". And every time the function exits the variable is lost and the name is forgotten.
As you may guess, because the variables are re-created every time the function is called, and nobody else will know them, there must be a secret place where they are stored. It could be called Chamber of Secrets or stack or local scope but it doesn't really matter. We know they are there, somewhere, hidden in the memory.
But, in JavaScript there is this very special thing that functions which are created inside other functions, can also know the local variables of their parents and keep them as long as they live.
var parent = function() {
var name = "Mary";
var child = function(childName) {
// I can also see that "name" is "Mary"
}
}
So, as long as we are in the parent -function, it can create one or more child functions which do share the secret variables from the secret place.
But the sad thing is, if the child is also a private variable of its parent function, it would also die when the parent ends, and the secrets would die with them.
So to live, the child has to leave before it's too late
var parent = function() {
var name = "Mary";
var child = function(childName) {
return "My name is " + childName +", child of " + name;
}
return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside
And now, even though Mary is "no longer running", the memory of her is not lost and her child will always remember her name and other secrets they shared during their time together.
So, if you call the child "Alice", she will respond
child("Alice") => "My name is Alice, child of Mary"
That's all there is to tell.
回答18:
I do not understand why the answers are so complex here.
Here is a closure:
var a = 42;
function b() { return a; }
Yes. You probably use that many times a day.
There is no reason to believe closures are a complex design hack to address specific problems. No, closures are just about using a variable that comes from a higher scope from the perspective of where the function was declared (not run).
Now what it allows you to do can be more spectacular, see other answers.
回答19:
Example for the first point by dlaliberte:
A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be used immediately. Therefore, the closure of the enclosing function probably already exists at the time that enclosing function was called since any inner function has access to it as soon as it is called.
var i;
function foo(x) {
var tmp = 3;
i = function (y) {
console.log(x + y + (++tmp));
}
}
foo(2);
i(3);
回答20:
A closure is where an inner function has access to variables in its outer function. That's probably the simplest one-line explanation you can get for closures.
回答21:
I know there are plenty of solutions already, but I guess that this small and simple script can be useful to demonstrate the concept:
// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
var _count = 0; // not accessible outside this function
var sequencer = function () {
return _count++;
}
return sequencer;
}
var fnext = makeSequencer();
var v0 = fnext(); // v0 = 0;
var v1 = fnext(); // v1 = 1;
var vz = fnext._count // vz = undefined
回答22:
You're having a sleep over and you invite Dan.
You tell Dan to bring one XBox controller.
Dan invites Paul.
Dan asks Paul to bring one controller. How many controllers were brought to the party?
function sleepOver(howManyControllersToBring) {
var numberOfDansControllers = howManyControllersToBring;
return function danInvitedPaul(numberOfPaulsControllers) {
var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
return totalControllers;
}
}
var howManyControllersToBring = 1;
var inviteDan = sleepOver(howManyControllersToBring);
// The only reason Paul was invited is because Dan was invited.
// So we set Paul's invitation = Dan's invitation.
var danInvitedPaul = inviteDan(howManyControllersToBring);
alert("There were " + danInvitedPaul + " controllers brought to the party.");
回答23:
JavaScript functions can access their:
- Arguments
- Locals (that is, their local variables and local functions)
- Environment, which includes:
- globals, including the DOM
- anything in outer functions
If a function accesses its environment, then the function is a closure.
Note that outer functions are not required, though they do offer benefits I don't discuss here. By accessing data in its environment, a closure keeps that data alive. In the subcase of outer/inner functions, an outer function can create local data and eventually exit, and yet, if any inner function(s) survive after the outer function exits, then the inner function(s) keep the outer function's local data alive.
Example of a closure that uses the global environment:
Imagine that the Stack Overflow Vote-Up and Vote-Down button events are implemented as closures, voteUp_click and voteDown_click, that have access to external variables isVotedUp and isVotedDown, which are defined globally. (For simplicity's sake, I am referring to StackOverflow's Question Vote buttons, not the array of Answer Vote buttons.)
When the user clicks the VoteUp button, the voteUp_click function checks whether isVotedDown == true to determine whether to vote up or merely cancel a down vote. Function voteUp_click is a closure because it is accessing its environment.
var isVotedUp = false;
var isVotedDown = false;
function voteUp_click() {
if (isVotedUp)
return;
else if (isVotedDown)
SetDownVote(false);
else
SetUpVote(true);
}
function voteDown_click() {
if (isVotedDown)
return;
else if (isVotedUp)
SetUpVote(false);
else
SetDownVote(true);
}
function SetUpVote(status) {
isVotedUp = status;
// Do some CSS stuff to Vote-Up button
}
function SetDownVote(status) {
isVotedDown = status;
// Do some CSS stuff to Vote-Down button
}
All four of these functions are closures as they all access their environment.
回答24:
The author of Closures has explained closures pretty well, explaining the reason why we need them and also explaining LexicalEnvironment which is necessary to understanding closures.
Here is the summary:
What if a variable is accessed, but it isn’t local? Like here:
In this case, the interpreter finds the variable in the
outer LexicalEnvironment
object.
The process consists of two steps:
- First, when a function f is created, it is not created in an empty
space. There is a current LexicalEnvironment object. In the case
above, it’s window (a is undefined at the time of function
creation).
When a function is created, it gets a hidden property, named [[Scope]], which references the current LexicalEnvironment.
If a variable is read, but can not be found anywhere, an error is generated.
Nested functions
Functions can be nested one inside another, forming a chain of LexicalEnvironments which can also be called a scope chain.
So, function g has access to g, a and f.
Closures
A nested function may continue to live after the outer function has finished:
Marking up LexicalEnvironments:
As we see, this.say
is a property in the user object, so it continues to live after User completed.
And if you remember, when this.say
is created, it (as every function) gets an internal reference this.say.[[Scope]]
to the current LexicalEnvironment. So, the LexicalEnvironment of the current User execution stays in memory. All variables of User also are its properties, so they are also carefully kept, not junked as usually.
The whole point is to ensure that if the inner function wants to access an outer variable in the future, it is able to do so.
To summarize:
- The inner function keeps a reference to the outer
LexicalEnvironment.
- The inner function may access variables from it
any time even if the outer function is finished.
- The browser keeps the LexicalEnvironment and all its properties (variables) in memory until there is an inner function which references it.
This is called a closure.
回答25:
As a father of a 6-year-old, currently teaching young children (and a relative novice to coding with no formal education so corrections will be required), I think the lesson would stick best through hands-on play. If the 6-year-old is ready to understand what a closure is, then they are old enough to have a go themselves. I'd suggest pasting the code into jsfiddle.net, explaining a bit, and leaving them alone to concoct a unique song. The explanatory text below is probably more appropriate for a 10 year old.
function sing(person) {
var firstPart = "There was " + person + " who swallowed ";
var fly = function() {
var creature = "a fly";
var result = "Perhaps she'll die";
alert(firstPart + creature + "\n" + result);
};
var spider = function() {
var creature = "a spider";
var result = "that wiggled and jiggled and tickled inside her";
alert(firstPart + creature + "\n" + result);
};
var bird = function() {
var creature = "a bird";
var result = "How absurd!";
alert(firstPart + creature + "\n" + result);
};
var cat = function() {
var creature = "a cat";
var result = "Imagine That!";
alert(firstPart + creature + "\n" + result);
};
fly();
spider();
bird();
cat();
}
var person="an old lady";
sing(person);
INSTRUCTIONS
DATA: Data is a collection of facts. It can be numbers, words, measurements, observations or even just descriptions of things. You can't touch it, smell it or taste it. You can write it down, speak it and hear it. You could use it to create touch smell and taste using a computer. It can be made useful by a computer using code.
CODE: All the writing above is called code. It is written in JavaScript.
JAVASCRIPT: JavaScript is a language. Like English or French or Chinese are languages. There are lots of languages that are understood by computers and other electronic processors. For JavaScript to be understood by a computer it needs an interpreter. Imagine if a teacher who only speaks Russian comes to teach your class at school. When the teacher says "все садятся", the class would not understand. But luckily you have a Russian pupil in your class who tells everyone this means "everybody sit down" - so you all do. The class is like a computer and the Russian pupil is the interpreter. For JavaScript the most common interpreter is called a browser.
BROWSER: When you connect to the Internet on a computer, tablet or phone to visit a website, you use a browser. Examples you may know are Internet Explorer, Chrome, Firefox and Safari. The browser can understand JavaScript and tell the computer what it needs to do. The JavaScript instructions are called functions.
FUNCTION: A function in JavaScript is like a factory. It might be a little factory with only one machine inside. Or it might contain many other little factories, each with many machines doing different jobs. In a real life clothes factory you might have reams of cloth and bobbins of thread going in and T-shirts and jeans coming out. Our JavaScript factory only processes data, it can't sew, drill a hole or melt metal. In our JavaScript factory data goes in and data comes out.
All this data stuff sounds a bit boring, but it is really very cool; we might have a function that tells a robot what to make for dinner. Let's say I invite you and your friend to my house. You like chicken legs best, I like sausages, your friend always wants what you want and my friend does not eat meat.
I haven't got time to go shopping, so the function needs to know what we have in the fridge to make decisions. Each ingredient has a different cooking time and we want everything to be served hot by the robot at the same time. We need to provide the function with the data about what we like, the function could 'talk' to the fridge, and the function could control the robot.
A function normally has a name, parentheses and braces. Like this:
function cookMeal() { /* STUFF INSIDE THE FUNCTION */ }
Note that /*...*/
and //
stop code being read by the browser.
NAME: You can call a function just about whatever word you want. The example "cookMeal" is typical in joining two words together and giving the second one a capital letter at the beginning - but this is not necessary. It can't have a space in it, and it can't be a number on its own.
PARENTHESES: "Parentheses" or ()
are the letter box on the JavaScript function factory's door or a post box in the street for sending packets of information to the factory. Sometimes the postbox might be marked for example cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime)
, in which case you know what data you have to give it.
BRACES: "Braces" which look like this {}
are the tinted windows of our factory. From inside the factory you can see out, but from the outside you can't see in.
THE LONG CODE EXAMPLE ABOVE
Our code begins with the word function, so we know that it is one! Then the name of the function sing - that's my own description of what the fu