JavaScript Closures and Higher Order Functions
JavaScript Closures and Higher Order Functions
Objectives
Explain what a closure is
Explain how a closure works
Practice using a closure
Describe use cases for closures in JavaScript
Introduction
As you may have seen, JavaScript gives us mechanisms to construct objects with a specific state. By that, we mean that we can construct objects such that some behavior is shared, and some data is different.
In the code above, we use a class constructor to create different objects. The objects share behavior, but vary in their data. Each item has a different manufacturePrice
. The marketMultiplier
represents the varying difference in price for different markets. For example, above our tennisShoe
was multiplied by 1.5
to accommodate for our New York and LA markets. In our suburban market, the marketMultiplier
of our tshirt
is 1
, or not marked up.
What may be surprising is that in JavaScript, we can also create functions that share specific capabilities but change others. So just like we can create objects as little units of work, we can also create functions.
How do you create a function? It's not too difficult, we simply return a function from another function.
Let's pay careful attention to what happened in the above code. We declared a function retailPriceMaker
whose return value is a function itself. The returned function takes an argument of marketMultiplier
and references manufacturePrice
(which is in its scope) and returns a retail price that is a function of the two.
Ok, so why would we want to do such a thing? Well, just like we have objects that we want configured in a special way, where we know some data at one time, but want to execute a method on the object at a different time, the same thing occurs with functions. Let's explore another example. Imagine we believe the manufacturePrice
of an item will be 3, and we want to experiment with its price in different markets.
By invoking retailPriceMaker
, we return a function. That function has its own unique attribute of a manufacturePrice
, which is passed in as an argument to retailPriceMaker
. We can see this value being passed in when retailPriceMaker(3)
is invoked.
The inner function takes in a second argument, marketMultiplier
, which we can see examples of when invoking retailPriceForThree(1.1)
and retailPriceForThree(1.5)
.
Earlier, we defined an Item
class that returns objects with a manufacturePrice
. Here, we defined a function that returns a function that has a manufacturePrice
value stored inside.
In JavaScript, functions can hold onto state in the same way that objects can. This makes sense, as functions are first class objects.
Ok, now let's take an even deeper look as to what is happening in the code. Look at the code again, below. As you see, retailPriceForNine
points to our returned JavaScript function. If you type retailPriceForNine
into the console you will see that function.
And if you type the manufacturePrice
into the console, you will see that it is not defined in the current global scope. Yet, somehow, when we execute this retailPriceForNine
function it knows that the manufacturePrice
is 9. It knows this, even though retailPriceForNine
points to a function that does not have the variable defined in its execution scope. So how does the function have a value for manufacturePrice
? Placing a debugger into our code and running it in our chrome console shows us.
We see that manufacturePrice
price is defined because of a closure. A closure is the attribute that all JavaScript functions have: JavaScript functions hold onto the scope that they had when they were declared. Let's take a look at our code again to see how we made use of a closure.
So every time we execute the retailPriceMaker
function we are declaring a new function. That's what our retailPriceMaker
function does: declare a function that it then returns. And when that function is declared, manufacturePrice
is in scope. So it doesn't matter that manufacturePrice
is not in scope when we later execute a function. There is a closure such that the function that retailPriceMaker
returns holds onto the scope it was declared with. This becomes a very powerful feature in JavaScript. Closures allow us to build functions that have their own capabilities.
So just like we can return a function called retailPriceForNine
and then see the retailPrice
returned with various marketMultiplier
s passed through, we can construct another function called retailPriceForSixteen
for, say, a different Item.
So as you see from above, our retailPriceMaker
function lives up to its name: it returns a function that calculates the retailPrice
for various markets. If we want to be able to calculate the retailPrice
for another retailPrice
, we can simply invoke our retailPriceMaker
again.
Privacy
Thus far, we have seen how we can use closures to return functions which have various attributes that they permanently hold onto. Closures are used for one other capability in JavaScript: privacy. As you can see above, once we invoke retailPriceMaker
and we pass through manufacturePrice
, it is impossible for us to ever change this attribute. This attribute is only even readable from inside the function, and we have defined our function in such a way that there is no other way to ever write the manufacturePrice
.
So here, our returned functions provide some capability that JavaScript objects do not: encapsulation. Remember that we can always change the data of an object.
But our attributes can be made truly private when using a closure.
Here, once we invoke our retailPriceMaker
to return the retailPriceForNine
function, we can never change that manufacturePrice
.
Another use case for closures occurs when we declare our classes. Because JavaScript classes are just syntactic sugar for functions, we can use closures with our classes as well. When would we want to do this? Let's modify our Item class a little.
As you see in the above code, we need to declare our ItemId
variable outside of our class. We do so because classes do not allow for private variables, only public methods. Yet we want a variable the Item
constructor can reference. The problem is that ItemId
and everything else can reference it as well. Let's change that.
The above code may look complicated, but our only change is to wrap the code in a function called createItem
. The createItem
function encapsulates all of the code declared inside of it. This prevents the ItemId
variable from being accessible outside of the createItem
function. It also privatizes the Item
class, so we make sure that we return that class from our createItem
function. Now when we execute the createItem
function, we assign the return value of the class to equal a constant called Item
. So Item
now points to our class, and we can call new Item
to construct a new instance of this class. Our use of closures comes into play every time we call new Item()
. When we construct a new instance, the constructor method references and modifies the ItemId
variable. Our constructor method can do so because when its class was declared ItemId
was accessible, and the class holds onto the variables in scope when it was declared. So using closures allows us to construct a class that has access to variables that are only available to functions that referenced the variable when the functions were declared. Thus it allows us to better create the scope that we want for ItemId
.
Summary
In this lesson, we explored an interesting feature of JavaScript functions closures. A closure is a feature in JavaScript such that a function holds onto the variables that it had access to when it was declared. Closures can be used to declare functions that have specific variables always defined. JavaScript developers also take advantage of closures to encapsulate data, as we can declare our functions in such a way that the data is only accessible from the returned function, with no way to overwrite the variables captured by the closure.
Last updated