Closure in JavaScript has developed a bit of a reputation over the years. Although it can be a bit tough to wrap your brain around initially, once you see in in action you’ll be surprised at how simple it really can be.
A Quick Refresher on Variable Scope
The first thing you should be familiar with is the idea of variable scope. As you may know, in JavaScript variables defined with var
are said to be scoped to the function they are defined in, or globally if they don’t exist in any specific function. This means that anything defined inside a function only exists inside that particular function. Let’s take a quick look at this in action.
var a = "outside the function";
function functionOne() {
var a = "inside the function";
console.log("logged from inside the function: " + a);
}
Here we have two different variables named a
. One is defined in the global frame outside of any functions, and one inside the functionOne
function. If we were to simply log the value of a
with console.log()
It would display one value:
console.log("logged from outside the function: " + a);
"logged from outside the function: outside the function"
But if we call functionOne
it will log a different value:
functionOne();
"logged from inside the function: inside the function"
These different values for a
are a result of the functional scope for variables defined with var
. As we’ll soon see, closure deals directly with the idea of variable scope, so it’s important to have a good understanding of the concept.
Nested Functions
A common practice in JavaScript is to nest one function inside of another. Functional scope for var
variables still applies in this situation, but something that might not be apparent is that nested functions have access to the variables of the ‘parent’ functions that contain it.
Let’s take a look at an example to see what’s going on:
function funcOne() {
var myString = "This string is defined inside funcOne";
function writeString() {
console.log(myString)
}
writeString();
}
funcOne();
Output:
This string is defined inside funcOne
Here’s a play-by-play of what happens when funcOne
is called:
funcOne
defines a variable,myString
, and a function,writeString
.funcOne
then calls thewriteString
function.writeString
simply logs the variablemyString
.
Note that writeString
has access to myString
automatically thanks to JS’s lexical scoping. WriteString
doesn’t define the variable nor is
it passed in as a parameter.
Your First Closure
So far so good, hopefully. Let’s change the code block above slightly to make use of closure:
function funcOne() {
var myString = "This string is defined inside funcOne";
function writeString() {
console.log(myString)
}
return writeString;
}
var myFunc = funcOne();
myFunc();
Output:
This string is defined inside funcOne
Here’s another play-by-play of what’s going on this time:
- The
funcOne
function defines themyString
variable as a string. - The
writeString
function is defined inside offuncOne
. All it does is logmyString
. - The
funcOne
function then returnswriteString
, without actually calling it. - The
myFunc
variable is defined as the output of thefuncOne
function. - The
myFunc
function is now called, which calls thewriteString
function that was defined insidefuncOne
. - The
writeString
function logsmyString
.
Okay, while obviously there’s a lot more going on here, from a compositional standpoint, it’s very similar code to the previous example. The key difference is that now instead of funcOne
defining and then running writeString
, it now defines it and returns the function itself.
That means when we define myFunc
as the result of funcOne()
, we’re essentially defining myFunc
as the writeString
nested function. Just as in the first example writeString
has access to the myString
variable, but so does myFunc
.
The key thing to understand here is that when a function returns another function that is nested inside of it, you’re not only returning the nested function, you’re returning the context in which the nested function was defined. This is what’s know as a closure. This is possible in JS because functions are first-class objects.
Closure Defined
Here’s a more formal definition that you may find helpful:
A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time that the closure was created.
One of the neat things that closures makes possible is functions that create other functions, but with the same initial conditions for every new instance.
var makeAdder = function(a) {
var adder = function(b) {
console.log(a + b)
}
return adder
}
var add5 = makeAdder(5);
With add5
we’ve created a version of the adder
function with the a
variable permanently set to 5. You can now pass any number to add5
and it will add 5 to it.
add5(2);
> 7
add5(7);
> 12
Thanks to closure we can use the makeAdder
function to make a completely
different adder:
var add10 = makeAdder(10);
With add10
the a
variable has been set to 10 and you can pass any number to add10
and 10 will be added to it.
add10(2);
> 12
add10(7);
> 17
Now that’s neat and all, but maybe not the most practical example. Let’s take a look at some instances where closures can be quite helpful in your production code.
Private Variables
Closures allow you to mimic some of the features of object-oriented languages that JavaScript lacks on its own such as private values.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
};
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
console.log(privateCounter);
}
}
};
This is very similar to the other closures we saw above. makeCounter
defines
an initial variable, privateCounter
, and a function, changeBy
, which has
access to privateCounter
. Instead of returning a single function as we saw
earlier, it actually returns an object which contains 3 functions. Two of these
functions call the ChangeBy
function, and as a result have access to
privateCounter
through the magic of closure.
We can now create a new counter with this code and use any of these methods to count up or down.
var counter1 = makeCounter();
counter1.increment();
counter1.value();
> "1"
counter1.increment();
counter1.value();
> "2"
counter1.decrement();
counter1.value();
> "2"
console.log(counter1.privateCounter);
> undefined
Notice that when we try to access the privateCounter
variable directly undefined
is returned. That’s because the variable is out of scope in that context, but the increment
, decrement
and value
functions still have access thanks to closure.
We could then create another separate counter if we wanted to, and it would not be affected be the first one, even though they both start with the same initial value.
var counter2 = makeCounter();
counter2.decrement();
counter2.value();
> "-1"
counter2.decrement();
counter2.value();
> "-2"
counter2.decrement();
counter2.value();
> "-3"
Using closure to create little tiny factories in this way is sometimes known the module pattern.
In Conclusion
One of the reasons closure and JavaScript are so closely associated with each other is that it’s not always possible in many other languages:
In JavaScript, if you use the function keyword inside another function, you are creating a closure. In C and most other common languages, after a function returns, all the local variables are no longer accessible because the stack frame is destroyed. In JavaScript, if you declare a function within another function, then the local variables can remain accessible after returning from the function you called.
So there you have it, as you can see, it’s really nothing too be intimidated by, even if you do want to do things a little more exciting than adding numbers together. We’re really just scratching the service with the ways that closure can be utilized in JavaScript programming, but this should give you a solid understanding to build on.