标签云

微信群

扫码加入我们

WeChat QR Code

JavaScript闭包如何工作?

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.


My problem with these and many answers is that they approach it from an abstract, theoretical perspective, rather than starting with explaining simply why closures are necessary in Javascript and the practical situations in which you use them. You end up with a tl;dr article that you have to slog through, all the time thinking, "but, why?". I would simply start with: closures are a neat way of dealing with the following two realities of JavaScript: a. scope is at the function level, not the block level and, b. much of what you do in practice in JavaScript is asynchronous/event driven.

2018年05月23日37分46秒

Redsandro For one, it makes event-driven code a lot easier to write. I might fire a function when the page loads to determine specifics about the HTML or available features. I can define and set a handler in that function and have all that context info available every time the handler is called without having to re-query it. Solve the problem once, re-use on every page where that handler is needed with reduced overhead on handler re-invocation. You ever see the same data get re-mapped twice in a language that doesn't have them? Closures make it a lot easier to avoid that sort of thing.

2018年05月23日37分46秒

For Java programmers, the short answer is that it's the function equivalent of an inner class. An inner class also holds an implicit pointer to an instance of the outer class, and is used for much the same purpose (that is, creating event handlers).

2018年05月23日37分46秒

Understood this much better from here: javascriptissexy.com/understand-javascript-closures-with-ease. Still needed a closure on closure after reading the other answers. :)

2018年05月23日37分46秒

I found this practical example to be very useful: youtube.com/watch?v=w1s9PgtEoJs

2018年05月23日37分46秒

Brilliant. I especially love : "A closure in JavaScript is like keeping a copy of the all the local variables, just as they were when a function exited."

2018年05月23日37分46秒

e-satis - Brilliant as it may seem, "a copy of all the local variables, just as they were when the function exited" is misleading. It suggests that the values of the variables are copied, but really it is the set of variables themselves which doesn't change after the function is called (except for 'eval' maybe: blog.rakeshpai.me/2008/10/…). It suggests that the function must return before the closure is created, but it need not return before the closure can be used as a closure.

2018年05月23日37分46秒

This sounds nice: "A closure in JavaScript is like keeping a copy of the all the local variables, just as they were when a function exited." But it is misleading for a couple reasons. (1) The function call does not have to exit in order to create a closure. (2) It is not a copy of the values of the local variables but the variables themselves. (3) It doesn't say who has access to these variables.

2018年05月23日37分46秒

Example 5 shows a "gotcha" where the code doesn't work as intended. But it doesn't show how to fix it. This other answer shows a way to do it.

2018年05月23日37分46秒

I like how this post starts off with big bold letters saying "Closures Are Not Magic" and ends its first example with "The magic is that in JavaScript a function reference also has a secret reference to the closure it was created in".

2018年05月23日37分46秒

feeela: Yes, every JS function creates a closure. Variables that are not referenced will likely be made eligible for garbage collection in modern JS engines, but it doesn't change the fact that when you create an execution context, that context has a reference to the enclosing execution context, and its variables, and that function is an object that has potential to be relocated to a different variable scope, while retaining that original reference. That's the closure.

2018年05月23日37分46秒

Ali I've just discovered that the jsFiddle I've provided doesn't actually prove anything, since the delete fails. Nevertheless, the lexical environment that the function will carry around as [[Scope]] (and ultimately use as a base for it's own lexical environment when invoked) is determined when the statement that defines the function is executed. This means that the function is closing over the ENTIRE contents of the executing scope, regardless of which values it actually refers to and whether it escapes the scope. Please look at sections 13.2 and 10 in the spec

2018年05月23日37分46秒

This was a good answer until it tried explaining primitive types and references. It gets that completely wrong and talks about literals being copied, which really has nothing to do with anything.

2018年05月23日37分46秒

Closures are JavaScript's answer to class-based, object oriented programing. JS is not class based, so one had to find another way to implement some things which could not be implemented otherwise.

2018年05月23日37分46秒

this should be the accepted answer. The magic never happens in the inner function. It happens when assign the outer function to a variable. This creates a new execution context for the inner function, so the "private variable" can be accumulated. Of course it can since the variable the outer function assigned to has maintained the context. The first answer just make the whole thing more complex without explaining what really happens there.

2018年05月23日37分46秒

I love this explanation, truly. For those who read it and don't follow, the analogy is this: the princess() function is a complex scope containing private data. Outside the function, the private data can't be seen or accessed. The princess keeps the unicorns, dragons, adventures etc. in her imagination (private data) and the grown-ups can't see them for themselves. BUT the princess's imagination is captured in the closure for the story() function, which is the only interface the littleGirl instance exposes into the world of magic.

2018年05月23日37分46秒

So here story is the closure but had the code been var story = function() {}; return story; then littleGirl would be the closure. At least that's the impression that I get from MDN's use of 'private' methods with closures: "Those three public functions are closures that share the same environment."

2018年05月23日37分46秒

icc97, yes, story is a closure referencing the environment provided within the scope of princess. princess is also another implied closure, i.e. the princess and the littleGirl would share any reference to a parents array that would exist back in the environment/scope where the littleGirl exists and the princess is defined.

1970年01月01日00分09秒

shouldn't it be var littleGirl = new princess() ?

2018年05月23日37分46秒

BenjaminKrupp I've added an explicit code comment to show/imply that there are more operations within the body of princess than what's written. Unfortunately this story is now a bit out of place on this thread. Originally the question was asking to "explain JavaScript closures to a 5yr old"; my response was the only one that even attempted to do that. I don't doubt that it would've failed miserably, but at least this response might've had the chance to hold a 5yr old's interest.

2018年05月23日37分46秒

Actually, confusingly, the makeKitchen function call is the actual closure, not the kitchen object that it returns.

2018年05月23日37分46秒

Having my way through the others I found this answer as the easiest way to explain about what and why the closures.is.

2018年05月23日37分46秒

Too much menu and appetizer, not enough meat and potatoes. You could improve that answer with just one short sentence like: "A closure is the sealed context of a function, for lack of any scoping mechanism provided by classes."

2018年05月23日37分46秒

I don't agree with your definition of what a closure is. There's no reason it has to be self-invoking. It's also a bit simplistic (and inaccurate) to say it has to be "returned" (lots of discussion on this in the comments of the top answer to this question)

2018年05月23日37分46秒

James even if you desagree, his example (and entire post) is one of the best I've seen. While the question is not old and solved for me, it totally deserve a +1.

2018年05月23日37分46秒

"I need to know how many times a button has been clicked, and do something on every third click..." THIS got my attention. A use case and the solution showing how a closure is not such a mysterious thing and that alot of us have been writing them but didn't exactly know the official name.

2018年05月23日37分46秒

Nice example because it shows that "count" in the 2nd example retains the value of "count" and not reset to 0 each time the "element" is clicked. Very informative!

2018年05月23日37分46秒

+1 for the closure behavior. Can we limit closure behavior to functions in javascript or this concept can also be applied to other structures of the language?

2018年05月23日37分46秒

I agree. Giving the functions meaningful names instead of the traditional "foobar" ones also helps me a lot. Semantics counts.

2018年05月23日37分46秒

so in a pseudo-language, it is basically like alert(x+3, where x = 5). The where x = 5 is the closure. Am I right?

2018年05月23日37分46秒

Jus12: exactly. Behind the scenes, a closure is just some space where current variable values (“bindings”) are stored, as in your example.

1970年01月01日00分01秒

This is exactly the sort of example that misleads many people into thinking that it is the values that are used in the returned function, not the changeable variable itself. If it were changed to "return x += y", or better yet both that and another function "x *= y", then it would be clear that nothing is being copied. For people used to stack frames, imagine using heap frames instead, which can continue to exist after the function returns.

2018年05月23日37分46秒

Matt I disagree. An example is not supposed to exhaustively document all properties. It is meant to be reductive and illustrate the salient feature of a concept. The OP asked for a simple explanation (“for a six year-old”). Take the accepted answer: It utterly fails at delivering a concise explanation, precisely because it attempts to be exhaustive. (I do agree with you that it’s an important property of JavaScript that binding is by reference rather than by value … but again, a successful explanation is one that reduces to the bare minimum.)

2018年05月23日37分46秒

By the way, I added this "answer" with clarifications not to address the original question directly. Instead, I hope that any simple answer (for a 6-year old) doesn't introduce incorrect notions about this complex subject. E.g. the popular wiki-answer above says "A closure is when you return the inner function." Aside from being grammatically wrong, that is technically wrong.

2018年05月23日37分46秒

James, I said the closure is "probably" created at the time of the call of the enclosing function because it is plausible that an implementation could defer the creation of a closure until sometime later, when it decides a closure is absolutely needed. If there is no inner function defined in the enclosing function, then no closure will be needed. So maybe it could wait until the first inner function gets created to then create a closure out of the enclosing function's call context.

2018年05月23日37分46秒

Beetroot-Beetroot Suppose we have an inner function that is passed to another function where it is used before the outer function returns, and suppose we also return the same inner function from the outer function. It is identically the same function in both cases, but you are saying that before the outer function returns, the inner function is "bound" to the call stack, whereas after it returns, the inner function is suddenly bound to a closure. It behaves identically in both cases; the semantics are identical, so aren't you just talking about implementation details?

2018年05月23日37分46秒

Beetroot-Beetroot, thanks for your feedback, and I am glad I got you thinking. I still don't see any semantic difference between the outer function's live context and that same context when it becomes a closure as the function returns (if I understand your definition). The inner function doesn't care. Garbage collection doesn't care since the inner function maintains a reference to the context/closure either way, and the caller of the outer function just drops its reference to the call context. But it is confusing to people, and perhaps better to just call it a call context.

2018年05月23日37分46秒

That article is difficult to read, but I think it actually supports what I am saying. It says: "A closure is formed by returning a function object [...] or by directly assigning a reference to such a function object to, for example, a global variable." I don't mean that GC is irrelevant. Rather, because of GC, and because the inner function is attached to the outer function's call context (or [[scope]] as the article says), then it doesn't matter whether the outer function call returns because that binding with the inner function is the important thing.

2018年05月23日37分46秒

Well played and answers the original poster. I think this is the best answer. I was going to use luggage in a similar way: imagine you go to grandma's house and you pack your nintendo DS case with game cards inside your case, but then pack the case inside your backpack and also put game cards in your backpack pockets, and THEN you put the whole thing in a big suitcase with more game cards in the pockets of the suitcase. When you get to Grandma's house, you can play any game on your DS as long as all the outside cases are open. or something to that effect.

2018年05月23日37分46秒

Wow, never knew you could use string substitutions in console.log like that. If anyone else is interested there are more: developer.mozilla.org/en-US/docs/DOM/…

2018年05月23日37分46秒

Variables that are in the function's parameter list are also part of the closure (e.g. not just limited to var).

2018年05月23日37分46秒

So could the main benefit of closures could be emphasized with this example? Say I have a function emailError(sendToAddress, errorString) I could then say devError = emailError("devinrhode2googmail.com", errorString) and then have my own custom version of a shared emailError function?

2018年05月23日37分46秒

After shoveling my way through a whole lot of 'splaining, I started to finally understand what they were for. I thought to myself "oh, its like private variables in an object?" and bam. This was next answer I read.

2018年05月23日37分46秒

What would happen if you called : second_calculator = first_calculator(); instead of second_calculator = make_calculator(); ? Should be the same, right?

2018年05月23日37分46秒

Ronen: Since first_calculator is an object (not a function) you should not use parentheses in second_calculator = first_calculator;, since it is an assignment, not a function call. To answer your question, there would then only be one call to make_calculator, so only one calculator would get made, and the variables first_calculator and second_calculator would both refer to the same calculator, so the answers would be 3, 403, 4433, 44330.

2018年05月23日37分46秒

If you read the description, you'll see that your example is not correct. The call to innerFunction is within the scope of the outer function, and not, as the description says, after the outer function returns. Whenever you call outerFunction, a new innerFunction is created and then used in scope.

2018年05月24日37分46秒

Moss that's not my comments, they're a Google developer's

2018年05月23日37分46秒

Seeing that innerFunction is not referenced outside outerFunction's scope, is the interpreter smart enough to see that it needs no closure?

2018年05月23日37分46秒

The code is "correct", as an example of a closure, even though it doesn't address the part of the comment about using the closure after the outerFunction returns. So it is not a great example. There are many other ways a closure could be used that don't involve returning the innerFunction. e.g. innerFunction could be passed to another function where it is called immediately or stored and called some time later, and in all cases, it has access to the outerFunction context that was created when it was called.

2018年05月23日37分46秒

syockit No, Moss is wrong. A closure is created regardless of whether the function ever escapes the scope in which it is defined, and an unconditionally created reference to the parent's lexical environment makes all variables in the parent scope available for all functions, regardless of whether they are invoked outside or inside the scope in which they were created.

2018年05月23日37分46秒

Nice addition, thanks. Just to make it more clear one can imagine how the "bad" array is created in the "bad" loop with each iteration: 1st iteration: [function () {return 'n = ' + 0;}] 2nd iteration: [(function () {return 'n = ' + 1;}),(function () {return 'n = ' + 1;})] 3rd iteration: [(function () {return 'n = ' + 2;}),(function () {return 'n = ' + 2;}),(function () {return 'n = ' + 2;})] etc. So, each time when the index value changes it is reflected in all functions already added to the array.

2018年05月23日37分46秒

Best working definition here!

2018年05月23日37分46秒

Using let for var fixes the difference.

2018年05月23日37分46秒

Isn't here "Closure done right" is an example of "closure inside closure"?

2018年05月23日37分46秒

I mean, every function is technically a closure but the important part is that the function defines a new variable within. The function that get returns just references n created in a new closure. We just return a function so we can store it in the array and invoke it later.

2018年05月23日37分46秒

This is the best explanation for JavaScript closures. Should be the chosen answer. The rest are entertaining enough but this one is actually useful in a practical way for real-world JavaScript coders.

2018年05月23日37分46秒

This is the explanation that made the most sense to me because it doesn't assume significant prior knowledge of technical terms. The top-voted explanation here assumes the person who doesn't understand closures has a full and complete understanding of terms like 'lexical scope' and 'execution context' - while I can understand these conceptually, I don't think I'm as comfortable with the details of them as I should be, and the explanation with no jargon in it at all is what made closures finally click for me, thank you. As a bonus, I think it also explains what scope is very concisely.

2018年05月23日37分46秒

This answer doesn't seem likely to help unconfuse people. A rough equivalent in a traditional programming language might be to create b() as a method on an object that also has a private constant or property a. To my mind, the surprise is that the JS scope object effectively provides a as a property rather than a constant. And you'll only notice that important behavior if you modify it, as in return a++;

2018年05月23日37分46秒

Exactly what Jon said. Before I finally grokked closures I had a hard time finding practical examples. Yes, floribon created a closure, but to uneducated me this would have taught me absolutely nothing.

2018年05月23日37分46秒

This does not define what a closure is -- it is merely an example that uses one. And it doesn't address the nuance of what happens when the scope ends; I don't think anyone had a question about lexical scoping when all the scopes are still around, and especially in the case of a global variable.

2018年05月23日37分46秒

FYI: running the above shows=> 9

2018年05月23日37分46秒

Small clarification about a possible ambiguity. When I said "In fact, the enclosing function does not need to return at all." I didn't mean "return no value" but "still active". So the example doesn't show that aspect, though it shows another way the inner function can be passed to the outer scope. The main point I was trying to make is about the time of creation of the closure (for the enclosing function), since some people seem to think it happens when the enclosing function returns. A different example is required to show that the closure is created when a function is called.

2018年05月23日37分46秒

That's only half the explanation. The important thing to note about closures is that if the inner function is still being referred to after the outer function has exited, the old values of the outer function are still available to the inner one.

2018年05月23日37分46秒

Actually, it is not the old values of the outer function that are available to the inner function, but the old variables, which might have new values if some function was able to change them.

2018年05月23日37分46秒

Seems that closure is equivalent to classes and inner classes in O.O.

2018年05月23日37分46秒

I agree: the said Mozilla page is particularly simple and concise. Amazingly enough your post has not been so widely appreciated as the others.

2018年05月23日37分46秒