Closures
Now that we have a firm grasp on how Scope works in JavaScript, it's time to turn our attention to a related topic, and one that trips up a lot of developers (myself included): closure.
Closure happens when a function is able to access its lexical scope even when it is executed outside of its lexical scope. This is typically done through a combination of an outer/external function which contains some variables and any number of inner functions that get returned from the outer function. When the outer function gets called, the inner function(s) can be invoked to access the variables declared in the outer function, even though those variables themselves aren't returned. The inner function is said to "enclose" the outer function's scope (including the internal variables). This is called a closure.
Let's take a look at a function and variable within an external variable's scope:
function aloha() { var greeting = "Howdy!";
function greet() { console.log(greeting); }
greet();}
aloha();// expected result: "Howdy!"
We declare an outer function, aloha()
, and within that a variable, greeting
, with the value "Howdy!"
as well as a function, greet()
that console.log()
s the greeting
variable. Finally we call the greet()
function. Then back outside of the external function we call aloha()
. This function call actually invokes the greet()
function, which accesses the greeting
variable, and we get "Howdy!"
logged out to the console. The greeting
variable and greet()
function are scoped to the aloha()
function. Nothing majorly complicated is happening here!
But now let's take it one step further and actually demonstrate closure:
function aloha() { var greeting = "Howdy!";
function greet() { console.log(greeting); }
return greet;}
var salutation = aloha();
salutation();// expected result: "Howdy!"
We made two crucial changes for the above to work:
- Rather than just calling the internal
greet()
function, the externalaloha()
function actually returns the internalgreet()
function - Rather than just calling the
aloha()
function globally, we assign the value returned by invokingaloha()
(i.e., the internalgreet()
function) to the variablesalutation
In this way we're calling greet()
outside of its authored lexical scope, but it still maintains its scope! And even though we aren't returning greeting
from the outer aloha()
function, we're able to access greeting
's value through the greet()
function. All of this illustrates a closure. Specifically, the greet()
function has closure over the contents of the aloha()
function.
Function Factory
Let's take a look at a more sophisticated example of this pattern:
function add(num1) { return function (num2) { return num1 + num2; };}
var add400 = add(400);var add600 = add(600);
console.log(add400(20));// expected result: 420
console.log(add600(66));// expected result: 666
We have our outer function, add()
which has a parameter, num1
. Within add()
we are returning an anonymous function, which has its own parameter, num2
. This anonymous function adds num1
and num2
and returns the value of that sum.
Below that we declare a variable, add400
, and pass it the add
function with the argument 400
. 400
will be our num1
.
Below that we do the same, but the variable declared is named add600
and 600
is passed as the argument. We're reusing the add()
function, but this time around 600
will be num1
.
Finally we actually call add400()
and add600()
, passing them the arguments 20
and 66
respectively. 20
and 66
will be num2
within our addition equations. 20
gets added to 400
because add400()
has closure over the add()
function with 400
as the value of num1
, and the resulting value is 420
. Similarly, 66
gets added to 600
because add600()
has closure over the add()
function with 600
as the value of num1
. The resulting value here is 666
.
We have essentially created a function factory with the add()
function using closures! It's a fancy way of being able to reuse functionality across your program.
Modules
Modules have been covered before on this blog, but to recap, the module pattern is a way of bundling related pieces of data and functionality within a program that can be reused or called when needed. To start with, we need a function. Let's look again at our aloha()
function from before:
function aloha() { var greeting = "Howdy!";
function greet() { console.log(greeting); }
greet();}
aloha();// expected result: "Howdy!"
At this moment there's nothing really special going on, we're calling the outer function and it's calling its inner function to log "Howdy!"
to the console. No closure, no module, just accessing some scope. But let's change it in a different way this time:
function Aloha() { var greeting = "Howdy!";
function greet() { console.log(greeting); }
return { greet, };}
var salutation = Aloha();salutation.greet();
This differs from our first closure example above in a few important ways:
aloha()
has now been capitalized toAloha()
. This is common practice for working with modules and other constructors. It signals to developers that you're deliberately embracing one of these code patterns- Where before we simply returned the
greet()
function withinaloha()
, here we have created an object to return and passedgreet
as a property on that object
Now when we declare the salutation
variable, we pass it the Aloha()
function call as a value, but in order to actually run the internal greet()
function, we have to call it as salutation.greet()
instead of simply salutation()
. greet
is made available as a method on salutation
.
This little example, while trivial, exemplifies the two requirements for the module pattern:
- There has to be an outer enclosing function that gets invoked at least once; each time it's invoked it creates a new instance of the module
- The enclosing function must return at least one inner function that has closure over the outer function's scope; it can access and modify values and functionality within that scope, but unless those values and functionality are deliberately returned from the outer function, they are treated as private
An example of the second point is the variable greeting
; the only way to actually access and use that variable is through the greet()
method on an instance of the module. As such, this is a way to keep certain data private but still available for use in your program.
Where we could invoke our Aloha()
module any number of times and create a new instance of the module each time it's invoked, we are also able to wrap the outer enclosing function within an IIFE and use that to invoke the module; this pattern only allows for a single instance of the module to be created:
var salutation = (function Aloha() { var greeting = "Howdy!";
function greet() { console.log(greeting); }
return { greet, };})();
salutation.greet();// expected result: "Howdy!"
You can think of the module's return
statement as its public API that allows you to modify the functionality or values within that module instance. This is super powerful stuff:
function Aloha() { var statement = "Howdy!";
function change() { statement = statement === "Howdy!" ? "Adios!" : "Howdy!"; }
function greetOrDismiss() { console.log(statement); }
var publicAPI = { change, greetOrDismiss, };
return publicAPI;}
var greetingOrFarewell = Aloha();greetingOrFarewell.greetOrDismiss();// expected result: "Howdy!"
greetingOrFarewell.change();
greetingOrFarewell.greetOrDismiss();// expected result: "Adios!"
We've added at change
method on the module instance that changes the greeting between "Howdy!" and
"Adios!"` when it gets called.
Closures and Loops
Before block scoped variables declared with keywords like let
and const
, closures were a way to help avoid scope pollution issues with loops in JavaScript.
Consider the following code, taken from my February post, Frontend JavaScript Pop Quiz:
for (var i = 0; i < 5; i++) { var btn = document.createElement("button"); btn.appendChild(document.createTextNode("Button " + i)); btn.addEventListener("click", function () { console.log(i); }); document.body.appendChild(btn);}
If you attach this code to an HTML doc, it appends 5 buttons (with the text "Button 0"
, "Button 1"
, "Button 2"
, "Button 3"
, and "Button 4"
), and attaches an event listener (specifically a click event listener) to each button to log i
to the console. However, with the code written as-is, rather than getting the number of the button that we clicked, we get 5
, regardless of if we click "Button 0"
or "Button 3"
or any other button. That's because the var i
variable in the for
Loop head has polluted the scope, and on the last iteration of the loop, i
's value gets incremented to 5.
There are lots of solutions to this issue, but one approach that embraces closure (and is therefore relevant to this blog post) is to use a callback function within an IIFE:
for (var i = 0; i < 5; i++) { var btn = document.createElement("button"); btn.appendChild(document.createTextNode("Button " + i)); (function wrapperIIFE(i) { btn.addEventListener("click", function logI() { console.log(i); }); })(i); document.body.appendChild(btn);}
We create an IIFE named wrapperIIFE
that has a parameter, i
. Within wrapperIIFE()
we have the click event listener attached to btn
(accessible because the IIFE is within the scope of the for
Loop), and that event listener's callback function, logI()
, has closure over the wrapperIIFE
, and thereby can access wrapperIIFE
's i
value in order to log it to the console. Lastly when we actually invoke the IIFE, we pass i
as its argument.
Now if we run this code we get the correct button numbers logged to the console when any of the buttons are clicked. This is still not my favorite solution – if you log i
in the console (outside of any loops or clicks), you'll still get 5
because the var
variable has still polluted the scope. The closure/IIFE solution here doesn't address the scope pollution issue.
Summary
A Closure happens when a function is able to access its lexical scope even when it is executed outside of its lexical scope. This is typically done through a combination of an outer/external function which contains some variables and any number of inner functions that get returned from the outer function.
One use for the closure pattern is to be able to make so-called "function factories" - functions that produce other functions; actual functionality gets reused, but different parameters can be used to generate different results. An addition function is a good example of this.
The module pattern is a specific use of closure, and is a powerful way of bundling related pieces of data and functionality within a program that can be reused or called when needed, while keeping data and functionality that you want hidden to stay secret within the module instance. There are two requirements to meet this pattern:
- There has to be an outer enclosing function that gets invoked at least once; each time it's invoked it creates a new instance of the module
- The enclosing function must return at least one inner function that has closure over the outer function's scope; it can access and modify values and functionality within that scope, but unless those values and functionality are deliberately returned from the outer function, they are treated as private
Closures also provide a way to address common problems surrounding scope and iterators in loops.