TLDR: Closure is the concept of storing a function and its environment together. When you create a function, it stores the functions local environment and its outer environment together. If you are ever confused about what value will be present, understand what value existed when the function scope was created!
Formal Definition
If you were to look up what a closure is, Wikipedia's definition has this to say in the first two lines:
In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.
That is a bit of a dense definition, but its not a complex as it seems at first glance! This article aims to explain what this means, bit by bit, so you can use closures with confidence.
Scoping
I first want to touch on what scoping means in JavaScript. Before ES6, JavaScript only had Global Scope and Function Scope. You have probably seen how variables are accessible based on what scope they were declared in. Here is an annotated example:
// Variable declared at the global scope.
var globalVariable = 'Neat';
function func() {
// Variable declared in function scope.
var funcVar = 12;
console.log(globalVariable);
}
console.log(funcVar);
func();
If you were to execute the above code, you would get a ReferenceError: funcVar is not defined
error. If you remove the console.log(funcVar);
line, the output would be Neat
. The reason for this is that scopes can only reference variable declared in their own scope (local) and any outer scopes relative to the current scope. In this case, the scope in func()
can access the outer scope (global) to get the value of globalVariable
, however the global scope does not have access to the scope created for func()
so it cannot access the funcVar variable. One more example to show how inner scopes can access values in outer scopes.
var globalVar = 'Hello';
function func() {
var innerVar = 'World';
function innerFunc() {
var name = 'innerFunc';
console.log(`${globalVar} ${innerVar}, from ${name}`);
}
innerFunc();
}
func();
Executing the above will show Hello World, from innerFunc
in the console. We can see that innerFunc()
has access to its local scope, the scope of func()
and the global scope.
Closure
The example above is actually a closure! It represents the second part of the Wikipedia definition, Operationally, a closure is a record storing a function together with an environment.
In this case, the function is innerFunc()
and the environment that is being stored is the local scope along with all of the outer scopes present at the time of function creation.
Thats it! If you have been writing functions, you have been creating closures this whole time!
Whats the Big Deal
The reason this can be a confusing topic is that closures can enable a handful of different patterns and ideas in JavaScript, even if they don't seem related at all. So here are some quick examples of things that are possible because of closures:
Access Data Through Interface
Say you wanted to create a simple counter with a variable representing the current count, and four functions: add, subtract, reset, show.
let count = 0;
const add = () => {
count = count + 1;
};
const subtract = () => {
count = count - 1;
};
const reset = () => {
count = 0;
};
const show = () => {
console.log('Count: ', count);
};
If you were to use these functions to add and show, like
add();
add();
add();
add();
show();
you would get Count: 4
. The issue is that if I were to throw in count = 0;
right before the show()
it would show Count: 0
! We are operating on a variable that any scope can access and modify, since it is global, and that is dangerous. Something can accidentally mess with count and cause a headache of a bug. This can be written in a different way:
const mkCounter = () => {
let count = 0;
const add = () => {
count = count + 1;
};
const subtract = () => {
count = count - 1;
};
const reset = () => {
count = 0;
};
const show = () => {
console.log('Count: ', count);
};
return {
add,
subtract,
reset,
show
};
};
This code is very similar, but you can see that we have declared it inside of a new function called mkCounter
that defined the count variable locally to its scope. At the end, we return an object that exposes the four functions but not the count variable, however since all of these functions are defined inside the mkCounter
scope, the closing environment for all of them contain count
! Here is how it would be used:
const counter1 = mkCounter();
const counter2 = mkCounter();
counter1.add();
counter1.add();
counter1.add();
counter1.subtract();
counter2.subtract();
counter2.subtract();
counter1.show();
counter2.show();
console.log(counter1.count);
which will give the output of:
Count: 2
Count: -2
undefined
Awesome, so not only can we not access the count as shown by the last line, each counter has its own count in their own environment to work with!
Partial Application
Edit: Updated this section thanks to @zaferberkun and @peerreynders in the comments!
Another closure example that I use all the time is partial application. A simple example could be formatting a log with some data that you don't want to set every time you invoke the function:
function logger(route, message, showDate) {
const header = showDate ? `${new Date().toISOString()} | ${route}` : route;
console.log(`${header} | ${message}`);
}
function mkLogger(route, showDate = false) {
// Implement "partial application" with the values
// in the closure
return (message) => logger(route, message, showDate);
}
Then you can use the function like:
const docLogger = mkLogger('DOCS', true);
docLogger('This is my log message');
docLogger('Another log message');
with the output of:
2021-11-15T23:55:26.672Z | DOCS | This is my log message
2021-11-15T23:55:26.672Z | DOCS | Another log message
This is nice because you can initialize things like the route and if you want to display the date when the program starts, then pass the simple docLogger
function to other parts of the application that need to use it instead of calling something like logger('DOCS', 'This is my log message', false)
every time you want to use it.
Other Uses
I just wanted to mention some other use cases that you can explore as well: Memoization, Singleton, Event Listeners.
Conclusion
Hopefully the concept of closure isn't too complex any more! If you do have any questions please let me know and I will do my best to address them and refine the article for clarity.
Top comments (5)
Strictly speaking, I don't believe your example of partial application is any different than the preceeding "counter" example--its just a higher order function that creates a closure when it returns the inner function. Partial application would be creating a logging function, and then creating another logging function that returns this existing logging function with some or the args already applied and the missing args supplied by the new function, thus the partial application or an already existing logging function. Correct me if im wrong or misunderstood your example.
Hi zaferberkun - thanks for the comment!
I can see what you are saying - I think I need to come up with a better example in this case to show the partial application, as the underlying
console.log
only takes the one argument.How about a simple add example in its place, where the example of the closure is in the
partial
function definition:Thanks for writing this up - ill add it to the main article and shout you out!
Hi Brett,
I like that! Also, after some consideration, I think I was actually wrong and your original example (and the one by peerreynders below) is a perfectly valid example of partial application.
What threw me was the use of an "intermediary" make function to create the partial. What I was expecting was that the function itself, logger in this case, could be applied partially "in place". You would have to add "undefined" arg checks, but that way you could do full or partial application without "intermediary" maker functions for each arg permutation. Of course, if you're planning using composition, you'll need to generate a partial that only takes one arg (which your logger make example is doing). Anyway, sorry about that, you can disregard my original post if you want.
Btw, great writing, hope you'll tackle some more functional programming concepts! I'm going to have to backtrack and read the ones you've already written.