One of the most important, but often misunderstood concepts in javascript is closures. Understanding closures unlocks programming patterns that would be difficult to use otherwise. In this post, we will understand what closures are, and go through a few examples to solidify our understanding of them.
The Lifecycle Of A Function
First, let’s take a look at a simple function:
function getValue(){
var a = 1
var b = 2
return a + b
}
var val = getValue()
console.log(val)
// Output: 3
The getValue
function instantiates two variables (a
and b
), and returns their sum. This return value is then stored in the val
variable and then printed.
Take a moment to think about the fate of a
and b
after the getValue
function returns.
These variables are only visible within the function scope, which means you can’t access a
or b
outside the body of the getValue
function. This means that once the getValue
function returns, there is no way to get the values of a
and b
, and they can be garbage collected.
Closures In Action
Now, let’s look at a different function:
function newCounter(){
var count = 0
return function(){
count += 1
return count
}
}
var counter = newCounter()
console.log(counter())
// Output: 1
console.log(counter())
// Output: 2
console.log(counter())
// Output: 3
The return value of newCounter
is actually another function. This function increases the value of count
(whose scope lies within the newCounter
function body) and returns it.
Similar to the last example, no one can access the value of count
outside the newCounter
function body. But, unlike the last example the function returned by newCounter
still has access to count
. This doesn’t break any rules, because the body of the returned function is still within the body of newCounter
.
count
can, therefore, be accessed indirectly by the returned function (which in our example is assigned to the counter
variable), but it is still closed off from any other code in the program that wants to access its value. The function body inside which the count
variable is closed off is called a closure.
How is this different from the variables a
and b
in our previous example? Well, since a
and b
are not referenced by any other function inside getValues
closure, they can be marked for garbage collection, and for all practical purposes, they cease to exist. The count
variable still exists in newCounter
’s closure, because it is referenced by the returned function, and so continues to exist in the systems memory after newCounter
returns.
Some More Examples
The best way, in my opinion, to understand closures is to see them in action. Let’s try to understand how they work, with a few more examples:
function greeting(){
var hello = 'hello'
var world = 'world'
function join(){
return hello + ' ' + world
}
return join()
}
console.log(greeting())
// Output: hello world
This looks very similar to our previous example, but there are no closures here! This is because unlike newCounter
, the join
function, which has access to the hello
and world
variables, is called, and exits, within the greeting
function body. greeting
only returns the result of calling the join
function.
If we modify the function a bit to return the join function, instead of it’s result, then hello
and world
would still exist inside greeting
’s closure:
function greeting(){
var hello = 'hello'
var world = 'world'
function join(){
return hello + ' ' + world
}
return join
}
var sayGreeting = greeting()
console.log(sayGreeting())
// Output: hello world
Let’s take a look at another example, where we use an objects method to get a return value:
function Counter(){
this.count = 0
this.getCount = function(){
this.count += 1
return this.count
}
}
var counter = new Counter()
console.log(counter.getCount())
// Output: 1
console.log(counter.getCount())
// Output: 2
Again, it may seem like this is doing the same thing as our previous counter
example, but the key difference here, is that this.count
is not closed off inside Counter
s closure. We can still access it if we run:
console.log(counter.count)
// Output: 2
If we modify the code to make count
a variable instead of an objects attribute, then it’s scope would be limited to Counter
s closure:
function Counter(){
var count = 0
this.getCount = function(){
count += 1
return count
}
}
var counter = new Counter()
console.log(counter.getCount())
// Output: 1
console.log(counter.getCount())
// Output: 2
console.log(counter.count)
// Output: undefined
Closures allow you to implement private variables in javascript. They also help with the functional aspect of javascript, because without them, it would be impossible to return functions that need access to variables defined at the time the function was declared.