JavaScript Primer: Comprehending Closures
Intro to closures, data protection, and partial function application
Closures are a core concept in programming, and they appear in many different languages. They have a number of use-cases, including adding flexibility to code and controlling access to sensitive data. Understanding closures and being able to implement them in whatever language you are using is an important skill for a developer. In this article we will take a look at how closures work in JavaScript, and cover some common use-cases. Through this article you will see how closures are core concepts for implementing callback functions and data protection. We will also look at using closures for partial function application.
What is a closure?
Broadly defined, a closure is a piece of code that is encapsulated along with elements from its surrounding context. That code can then be passed around in the program, and executed somewhere else. When it is executed, the code in the closure can access data from its original environment, even if that data is inaccessible in the new execution context.
Exactly how closures are implemented differs from language to language. While Ruby uses blocks and a special object called a “proc” to generate closures, JavaScript builds the concept of a closure into the behavior of its functions. Specifically, a closure in JavaScript is a function definition combined with all of the in-scope variables that it uses. Let’s demonstrate this with a simple example:
On lines 1–3 we declare three variables — city
, forecast
, and news
— and initialize them to string values. Because they are declared in the top-level scope, they are available to be referenced anywhere in the program; they have global scope. On lines 5–7 we declare a function reportWeather
, which logs a string to the console. The expression passed to console.log()
is a template literal which interpolates the values of city
and forecast
into the string. Where is the concept of a closure in this code?
When reportWeather
is declared it recognizes two things: (1) it needs city
and forecast
in order to execute, and (2) both of those variables exist in the current scope. Based on these observations, reportWeather
forms a closure that includes these two variables names. On line 9, reportWeather()
is invoked. When line 6 executes, reportWeather
looks in its local scope (inside the function body) to see if any variables named city
and forecast
are declared there. Since it does not find them, it then checks the variable names in its closure. city
and forecast
exist in the closure, so reportWeather
can use those variable names to lookup the current value of those variables.
Notice that although the variable news
is also in-scope when the function is defined, it is not included in reportWeather
's closure. Functions only form closures with variables that they actually need in order to run.
Closures are formed lexically
Another characteristic of JavaScript closures is that they are formed according to the structure (or layout) of the program. The closure is not created at runtime. This trait is often referred to as a lexical trait. Check it out — pay special attention to the value of news
and the output of reportNews()
:
The lexical nature of closures is the reason why reportNews()
outputs "BREAKING! Blazers win!"
instead of "BREAKING! Thorns win!"
. When the reportNews
function is declared on lines 3–5, if forms a closure with the news
variable declared on line 1. The closure references that variable in particular. That top-level news
variable is shadowed inside the block on lines 7–11 by another variable with the same name. This locally-scoped news
is the variable that is in-scope when reportNews()
is invoked. However, since reportNews
has a closure with the news
variable from line 1, it looks up the value of that variable when it executes. This demonstrates that the closure was formed according to where reportNews
was defined, not where it was eventually invoked.
Closures contain references
When a closure is formed, it does not capture the values of the variables which are part of it. The closure only contains references to the variables. This has important consequences. Although a closure is formed lexically, the value of each variable in the closure is retrieved at the time the function executes. This is part of what makes closures so powerful — the values of the variables in the closure can change during runtime:
When reportNews
is defined on lines 3–5, it forms a closure with the news
variable. The closure contains the identifier news
, but not the value 'Blazers win'
. On line 7, news
is re-assigned to a new string 'Thorns win'
. When reportNews()
is called on line 9, it looks up the current value of news
, which is 'Thorns win'
, and interpolates it into the template literal.
The fact that JavaScript closures have both lexical character and runtime character can lead to confusion. To keep everything straight, take note of which variables are in-scope where the function is defined. After that, pay attention to any changes that happen to those variables before the function is invoked.
Callback functions
Closures are a great solution to a variety of problems. The most common use-cases include callback functions, data encapsulation, and partial function application.
Callback functions are probably the most common places that a developer will encounter closures. A callback function forms a closure with variables in its surrounding context, and makes those variables available to a method that wouldn’t have access to them otherwise. Here’s a quick example:
On lines 1–2, we declare variables numbers
and increment
, and initialize them to an array and a number respectively. On line 4, a variable biggerNums
is declared with an initializer. In the initializer, map()
is called on the array referenced by numbers
, and an anonymous arrow function is passed to map()
as a callback function argument. Since increment
is not in-scope where the map()
method is defined (it is in-scope where map()
is invoked, but not where it is implemented), map()
does not naturally have access to increment
. However, the callback function has increment
as part of its closure, so it is able to look up the value of increment
each time map()
invokes the callback function. This allows map()
to use increment
’s value even though map()
does not have access to it directly.
Securing data
Closures are a powerful tool for implementing data protection. Typically, a program that handles sensitive information should prevent it from being easily accessed or changed. Instead, there should only be a few well-defined pathways for reading or updating the data. Writing a function which returns a closure is one way build that functionality.
To illustrate this, let’s write a small program that handles a credit score. In general there two ways to check someone’s credit score. The first is a soft-inquiry, which consumer sites like Credit Karma tend to use. A soft inquiry can look up the credit score without affecting its value. When you go to apply for a loan however, the lender will perform a hard-inquiry, which can affect the credit score by a small amount. For our program, these are the only two ways to access the credit score. We don’t want anyone to have access to the data without going through one of the two channels we define.
Let’s define a function called createCheckers
which generates a credit score and returns two functions that can access that value:
On lines 4–6 and 8–11 we declare two constants — softInquiry
and hardInquiry
— and initialize them to anonymous function expressions. Both of these functions form closures that include a reference to the creditScore
variable declared on line 2. When createCheckers()
is invoked on line 16, it returns these functions as a two element array, and we use array destructuring to assign each of them to a newly declared variable, softInquiry
and hardInquiry
. The functions represented by softInquiry
and hardInquiry
are now the only ways to access the value of creditScore
— its scope is confined to the createCheckers
function body.
softInquiry()
is called on line 18, and since it has a closure with creditScore
, it can look up its value and return it on line 5. Next, we invoke hardInquiry()
on line 19. This function decrements creditScore
by 5
on line 9 before returning its value on line 10. When we perform another soft inquiry on line 20, we see that the credit score was indeed harmed, and it remains 595
after the hard inquiry. Using closures, we were able create a system where a value can only be accessed in two ways: it could be either read without detriment, or it could be read at the cost of 5 points. No other manipulation of the credit score is possible.
Partial function application
In my opinion, this is one of the trickier ways that closures are used in JavaScript. We will look at one simple example, and leave a deeper dive for another day. Let’s say that we have a function that accomplishes some task. The only problem is that the function requires a certain minimum number of arguments to work properly, and the API we are using to call the function can’t pass enough arguments. We can use a closure to “apply” some of the arguments to our function before it is actually invoked in the program. Take a look:
On lines 1–3 we have a function joinWords
which expects two string arguments and returns the result of concatenating them. This works great until we encounter a situation where we want the functionality of joinWords
, but can only pass one argument. This takes place on lines 12 and 13 where we call a function called greet
, which only takes one argument — a name — but needs to output a string that also has a greeting.
We accomplish this by defining a function createGreeter
which takes the greeting as an argument, and returns an anonymous function which uses that greeting
value to call joinWords
. When that anonymous function is created, it forms a closure with the greeting
variable, and “fixes” the reference to that variable to the joinWords
argument list. When createGreeter
returns on line 11, it returns a function definition that can take a single argument. When that function which is now assigned to greet
is invoked, it takes a single argument, using it and the greeting
variable from its closure to call joinWords
with two arguments. We have successfully created a way to get the functionality of joinWords
with a function call that has fewer arguments than joinWords
requires. This is the essence of partial function application.
Hopefully by now the utility of closures is clear. They are a core component of JavaScript, and for good reason. In the course of using the language you are almost sure to use them, even if you don’t realize it at the time. Having the ability to recognize closures will make understanding and debugging code much more straightforward. Being able to implement them to solve a problem adds a indispensable tool to your tool belt. Practicing closures until they become intuitive is well worth the time and effort it requires.
This is last of three articles that I wrote concerning fundamental topics in JavaScript. Feel free to check out the first two JavaScript Primer articles in this series: variables and scope, and hoisting.