Seeing as MDN’s page on closures defines it extremely well, I’m not going to reinvent the wheel here:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
Let’s unpack this concept a bit.
The first thing I want to point out is that whenever you define a function, you are making a choice about its lexical environment, whether intentional or unintentional, conscious or…maybe you’re unconscious. I don’t know.
In many cases, the lexical environment of a function is just the global scope, because that is where you created it. Don’t worry, there’s nothing wrong with that.
But the value of a closure becomes more obvious when you intentionally enclose a function into its own lexical environment. One of the ways you can do this is by wrapping it inside another function. You’ve now created a more specific, local scope.
Here’s an example:
Here we have a
rocket function, which has some local variables, and an inner
fly function. When I call
rocket, it returns a unique instance of an object with the
fly function stored inside that object (line 15). It’s important to note that I’m not actually calling
fly yet. I’m just returning an object that has the
fly function inside it.
Each time I call the
rocket function, I’m returning a separate instance of the
fly function, and that instance of
fly is actually holding on to its own instance of its lexical environment, even after
rocket has completed running.
That right there is the power of a closure: Each time I call
rocket, I’m creating an instance of
fly that has a persisting reference to a unique lexical environment.
You can see how this specific use case could be valuable if you’re trying to do something object oriented. We’ve encapsulated state and behavior in a persisting, reusable manner! And we didn’t use
new. How about that.
One other nice thing about doing object oriented programming in this manner is that your state is actually private and inaccessible compared to when you use class syntax from ES2015. You can’t access the name of a rocket with
sputnik._name (unless you added it to the returned object of
rocket on line 15, but you shouldn’t do that).
The power to keep private state truly inaccessible is a really good thing. It tells readers how the function should and should not be used. Keep private variables private. This also gives you the option to implement a true “read only” paradigm, if you wanted to.
I’ve added name access in the object returned by
rocket on lines 17–19, using
get syntax. I can now read the
_name property, but I don’t have the ability to change it. That’s exactly the kind of guardrail that I wanted to implement. Great success!
Sorry for the tangent on private state. I just think it’s one of the cooler things closures make possible.
Here’s one last example of a closure in action:
Here we have a higher-order function (a function that operates on other functions), called
moduloFactory. It’s a function that returns other functions, so you could also call it a function factory (thus the word factory in the name).
So where is the closure? In this example, the closure is the combination of the anonymous function returned by
moduloFactory (line 2), and the saved lexical environment that this anonymous function keeps in its back pocket, in the form of the argument passed to
moduloFactory at the time it was called.
So because of closures, we can pump out as many unique instances of that inner function as we want, and each one will keep a reference to whatever lexical environment we choose.
Closures are cool, right? They were there all along, you just didn’t notice them. They’re not attention seekers. They’re wallflowers. You kind of have to seek them out, before you really get a sense of who they are. But like most introverts, they are worth the time and effort it takes to get to know them.
Hopefully I’ve made a nice introduction. 😅
This article is part of a series on digestible code concepts for folks newer to web development, who are searching for better approaches to solving problems with code.