Probably this is one of the most dreaded topics to talk about. Quite a few readers won't even bother to read on but hey! hang in there and we will keep things simple. To explain what closures are, I won't begin with the classic ubiquitous example of a silly function within another function (keeping it for later though). Well, closures also play a very obvious role when implementing event handlers (DOM Level 2). Lets talk with an example, shall we?
Try this out on codepen
There's nothing much to the code shown above, its as simple as a function that takes in an integer and sets event listeners on two different buttons. The first event listener calls a function (callback) when the first button is clicked. The callback function in turn increments the counter and shows it in an alert box. The second event handler does nothing different apart from the fact that its callback function decrements the same counter. Do we understand that even after the function setClickHandler has finished execution (offboarded from the execution stack with all its variables and arguments), the two event listeners are actively listening for a click on their respective buttons? Well, that's how event listeners work, right? You set them up once and they stay there for you unless you call "removeEventListener" on the node. There's another interesting thing to observe in the small code snippet. When the function "setClickHandler" has already left the call stack (no arguments and local variables in memory anymore), why do the callback functions not throw a "ReferenceError" when trying to access the argument "counter" which effectively lies in the lexical scope of "setClickHandler" ? Well, if you want to attribute this to the scope chain, you won't be completely wrong but then when I ask you how does something up in the scope chain remain active in memory after the owner function has offboarded the execution stack? That's when you need to say CLOSURES.
What is a closure?
Lets try to gain some clarity on stuff we discussed so far. The MDN says a function along with references to its surrounding scopes is a closure for that function. Well, won't you then protest that literally every function in JavaScript by default gets access to everything in its respective lexical (surrounding) scope? If that's true then every function has to have a closure as soon as its created. Bingo! guess who is correct again? Exception being functions created with the Function constructor. We usually tend to ignore a function's ability to fom closures when the function finishes its execution well within its lexical scope. What if the same function gets returned from its lexical scope and is refrenced somewhere else later in time? The function will still have access to the references (memory location of variables and functions) in its lexical scope as it would even if it were not returned. Wait! how? Didn't the scope chain die when the outer function finished execution? Infact, it did! but before it died, right when the inner function was being returned, the scope chain was saved as a property ( or an internal slot as ECMAScript quotes ) of the inner function itself. The specification calls it [[Environent]], however Google Chrome uses something like ( [[Scopes]]: { closure } ). It seems chrome did this back in time following the old specification from 2011. Just to mention, Firefox and IE don't even show a function's inner slot like chrome does. Alright, so this saved scope chain is nothing but Closure. That's how an inner function closes over the references in its surrounding scope.
If you try to inspect such a function's execution in devtools, you shall notice that as soon as the function is pushed on the top of the execution stack, its closure shows up in the scope chain as if the function carried it throughout. If you're 5, a closure is a function who took notes ( [[Environent]] ) to remember what has been taught in the school, outside the school.
Detect a Closure
If you really want to visualize this, the easiest way is to use an online visualizer. I will keep it simple and use chrome devtools instead.
Inner function returned from the Outer funtion
It's not necessary that the inner function be returned to form a closure
Chrome does not show the inner function in the Local scope?
Conclusion
Well, now you know how closures are formed. The last example can be a bit confusing. So to clarify, since Chrome does not show the inner function in the outer function's local scope, it does not mean that it doesn't exist (You can see that on Firebox). Chrome probably lists the function only when it has been invoked, stored or returned. Similarly, every function when created forms a closure with its surrounding scope but we only care about closures when the function escapes its lexical scope to execute at some point later in time after its surrounding function is dead. In the end, it shouldn't matter much whether you say all JavaScript functions are closures or all JavaScript functions have closures. You get the gist of it. Here's an interesting codepen that demonstrates function currying, a good practical use-case of closures. I would also recommend reading the performance considerations on MDN.
Top comments (9)
Is everything in Javascript an Object ?
YES
@pratiksharm Well, thank you for opening a good discussion. I am afraid its technically not a good idea to call everything in JS an object. Even though we've got object wrappers to make certain methods work flawlessly on primitives, its still better to keep the difference well lit. Such assumptions can lead to mistakes where someone would try assigning a property to some primitive expecting it to be retrievable which in this case will misfire :)
Correct.
5 !== new Number(5)
. Values and references aren't exactly the same. For most purposes, you can treat everything like an object in JS, but it's nuanced.I mean, primitives get
Object
methods, so....@baenencalin Hey! I just tried to make this a little more clear in this post
I see. And it's a great post.
:)
Is everything in Javascript an Object ?
NO
Are all JavaScript functions Closures
Short answer -- yes.