Functional JS #4: Closures
We have already covered a lot of ground when it comes to functional programming terminology.
It is time to turn our attention to another concept that is useful when trying to program in a more functional fashion. We will be talking about closures.
Some time ago, we have introduced a notion of first-class functions, as well as higher-order functions. We have seen how they can be used to compose our programs’ complex logic out of multiple small, single-purpose functions. We will now see what’s the relationship between these concepts and closures — and why higher-order functions wouldn’t be nearly as useful without closures.
Last time, we have focused on application state and pure functions. Today, we are going to discuss how we can model and store application state using closures.
But enough with the mystery…
What is a Closure?
A closure is a mechanism present in some programming language that allows functions to “remember” the variables that were present in their outer scope when they were defined.
Okay, that’s a lot to process. Let’s take it from the top.
What do we mean by “their outer scope”? There are a couple of different “scopes” that variables/constants can be defined in. Let’s take a look at a few of them.
The first, and the most straightforward of them is local scope:
Take a closer look a line 3. Hopefully, we can all intuitively understand how the
localGreeting variable is available there – it has been defined just a line earlier, in the same function. It belongs to the
userGreetingMessage's local scope.
Then, there’s global scope:
Here, we have access to the
globalGreeting variable in line 4 because it has been defined in the global scope of the codebase – so it is accessible everywhere.
So far, so good
The previous examples represent common patterns that hopefully are easy to understand. Now, let’s turn our attention to closures.
First, we will take a look at a (contrived example of) higher order function.
Here, we can see that the
closureGreeting can be referenced in line 5, even though it is not defined in the local scope of the inner (lines 4-7) function, or in the global scope of the application.
Let’s try to follow some of the execution steps here:
- The outer function (
makeGreeter) is executed in line 10.
- Following the way code is run, we need to turn our attention to the body of
makeGreeterin line 2.
closureGreetinggets initialized in the
makeGreeter's local scope.
- We define and immediately return the inner function.
- The inner function gets assigned to
userGreetingMessageand line 10 completes.
- The inner function is executed in line 12.
Hello, Krzysztofis printed.
In line 12, the outer function (
makeGreeter) has already exited, so we could expect the
closureGreeting variable to be gone. But this is not what happens – we can see that the inner function still has access to the
This is possible because of closures.
A closure is a mechanism that allows inner functions to remember the variables that were present in their outer scope when they were defined.
We can see closures in action by using
In the screenshot, 3rd line from the bottom, we can see that the
userGreetingMessage function remembers the value of
closureGreeting inside its
[[Scopes]] attribute. This is what is referenced to when the function is called.
A different take on closures
There are different ways of looking at closures that might be helpful when trying to understand how they work.
One way to recognize closures is to keep in mind that every time there is a function defined inside another function — the inner one has access to variables defined in the outer function.
This applies to variables explicitly defined in the outer function (as we’ve seen earlier), but also to arguments of the outer function. Also, all of this applies to arrow functions as well as the standard
An example showcasing this:
We can also look at closures as first-class functions with bound variables.
There is one thing we need to be aware of when trying to follow the flow of a piece of code that uses closures. Consider the following example:
Running this code will yield
Hi, Krzysztof instead of
Hello, Krzysztof we might be expecting.
This is because inner functions “remember” values of variables at a point of time when the outer function returned, and not when the inner function was defined.
In practice, we shouldn’t be reassigning variables as we did here, but that’s just something worth keeping in mind.
If closures look like a pretty complex subject, that’s because they are. The good thing is, they quickly become intuitive once we start using them.
Nevertheless, one has to wonder: why do we need closures in the first place? Let’s explore some use cases.
Function composition, higher-order functions
Using closures can help us write more readable code in a functional way — using generic functions to create more specialized ones.
Consider this example:
getObjectAttributeByName, as well as some of the functions discussed earlier, represents a common way of defining multi-parameter functions – using currying. We will learn more about this technique in the future, but for now, you can hopefully understand what is going on.
getObjectAttributeByName in line 3, providing the
attributeName argument. What is returned is an inner function that accepts
obj as a parameter, and returns
By the time we call the inner function,
getFirstName, in line 7, the
getObjectAttribute function has already returned. However, thanks to closures, the information about
attributeName being equal to
'firstName' is still there and can be used.
There are more benefits to using currying and, a related technique, partial application. We will focus on that in the next parts, but we need to remember that it is all made possible (or rather, useful) because of closures. If not for them, the inner functions could not access outer functions’ arguments and variables.
There is one additional benefit of using closures — they can be used to achieve encapsulation.
Consider this example, inspired by Douglas Crockford:
This looks trivial. There is one important consequence of using closures for storing
currentValue. Once we execute the
counter function, there is no way for us to access
currentValue directly and, say, modify it later.
This example is, of course, contrived (in addition to having side effects) — but we can see how this pattern can apply to more complex data structures as well.
We could use a closure to only expose a few “interface” functions to interact with and keep the internal data representation inaccessible. This would not have been possible if we used, say, plain objects to model such behavior.
There are two final points to keep in mind regarding closures:
- We haven’t touched upon what happens when functions are defined in non-standard ways, like using
Function()constructor. It is not that important since these are not (shouldn't be) used often.
- The word closure is being used to reference two slightly different things: 1) the set of variables of the outer function, and 2) the inner function with this set of bound variables.
We now understand what the mysterious term closure means, and how it can be useful for encapsulation and function composition.
If you want to learn more about closures, I recommend these resources:
I hope this comes in handy. See you in the next part!