What is a closure? Example use cases in JavaScript and React
Matt
Posted on May 9, 2022
What is a closure?
If you are not completely new to JavaScript and are unfamiliar with closures, you have probably used a closure without knowing it. A closure is when a function has access to variables (can read and change them) defined in its outer scope, even when the function is executed outside of the scope where it was defined. A closure is a function enclosing a reference (variable) to its outer scope. Functions can access variables outside of their scope.
Here is a simple example where an outer function that returns an inner function has access to a variable in the outer function:
function outerFunction() {
let outerFuncVar = "outside";
function innerFunction() {
console.log(`The value is: ${outerFuncVar}`);
}
return innerFunction();
}
outerFunction();
Console output: The value is: outside
The outer function returns an inner function that "closes" over the outer function variable outerFuncVar
. This is why it is called a closure. The outerFunction
, which returns the innerFunction
, can be called anywhere outside of its scope and the innerFunction
will have access to, it can remember, the outerFuncVar
. When it is called, it can read the value of this variable.
Let's modify the above example so that the outerFunction
variable can be changed and new value is logged after 5 seconds has passed:
function outerFunction(input) {
let outerFuncVar = input;
function innerFunction() {
setTimeout(() => {
console.log(`The value is: ${input}`);
}, 5000);
}
return innerFunction();
}
outerFunction("new value");
Console output: The value is: new value
Even after outerFunction
has finished executing in the above example, the outerFuncVar
is still accessible 5 seconds after the function was called. JavaScript automatically allocates memory when variables are initially declared. After a function returns, its local variables may be marked for garbage collection and removed from memory. Garbage collection is a type of automatic memory management used by JavaScript to free memory when an allocated block of memory, such as a variable and its value, is not needed anymore.
If the outerFuncVar
was garbage collected right after the function call, it would cause an error because the outerFuncVar
would no longer exist. The outerFuncVar
is not garbage collected because JavaScript works out that the nested innerFunction
may still be called as it is used in a closure. JavaScript does memory management for us, unlike low-level languages such as C.
You can also see this persistence of the closures reference to an outer variable by returning the innerFunction
from the outerFunction
and storing it in a variable before executing the innerFunction
:
function outerFunction() {
let outerFuncVar = "outside";
function innerFunction() {
console.log(`The value is: ${outerFuncVar}`);
}
return innerFunction;
}
const innerFunct = outerFunction();
innerFunct();
Console output: The value is: outside
If the outer function is a nested function itself , such as outerOuterFunction
in the code below, all of the closures will have access to all of their outer function scopes. In this case the innerFunction
closure has access to the outerFunction
and outerOuterFunction
variables:
function outerOuterFunction() {
let outerOuterFuncVar = "outside outside";
return function outerFunction() {
let outerFuncVar = "outside";
function innerFunction() {
console.log(`The outerFunction value is: ${outerFuncVar}`);
console.log(`The outerOuterFunction value is: ${outerOuterFuncVar}`);
}
return innerFunction;
};
}
const outerFunct = outerOuterFunction();
const innerFunct = outerFunct();
innerFunct();
Console output:
The outerFunction value is: outside
The outerOuterFunction value is: outside outside
Multiple instances of a closure can also be created with independent variables that they close over. Let's look at a counter example:
function counter(step) {
let count = 0;
return function increaseCount() {
count += step;
return count;
};
}
let add3 = counter(3); // returns increaseCount function. Sets step and count to 3
let add5 = counter(5); // returns increaseCount function. Sets step and count to 5
add3(); // 3
console.log(add3()); // 6
add5(); // 5
add5(); // 10
console.log(add5()); // 15
When the counter
function is called using counter(3)
, an instance of the increaseCount
function is created that has access to the count
variable. step
is set to 3, it's the function parameter variable, and count
is set to 3 (count += step
). It is stored in the variable add3
. When the counter
function is called again using counter(5)
, a new instance of increaseCount
is created that has access to the count
variable of this new instance. step
is set to 5 and count
is set to 5 (count += step
). It is stored in the variable add5
. Calling these different instances of the closure increments the value of count
in each instance by the step
value. The count
variables in each instance are independent. Changing the variable value in one closure does not effect the variable values in other closures.
A more technical definition of a closure
A closure is when a function remembers and has access to variables in its lexical / outer scope even when the function is executed outside of its lexical scope. Closures are created at function creation time. Variables are organized into units of scope, such as block scope or function scope. Scopes can nest inside each other. In a given scope, only variables in the current scope or at a higher / outer scope are accessible. This is called lexical scope. Lexical, according to the dictionary definition, means relating to the words or vocabulary of a language. In this case, you can think of it as how scoping occurs in the JavaScript language. Lexical scoping uses the location of where a variable is declared in the source code to determine where the variable is available in the source code. Scope is determined at compile time, more specifically lexing time, by the compiler of the JavaScript Engine used to process and execute the code. The first stage of compilation involves lexing / parsing. Lexing is when the code is converted into tokens, which is part of the process of converting code into machine readable code. You can read about how the JavaScript engine works in this article: JavaScript Visualized: the JavaScript Engine.
Why are closures important? Some examples
Here are a few examples of where closures are used in JavaScript and React.
JavaScript
Async code
Closures are commonly used with async code, for example: sending a POST request using the Fetch API:
function getData(url) {
fetch(url)
.then((response) => response.json())
.then((data) => console.log(`${data} from ${url}`));
}
getData("https://example.com/answer");
When getData
is called, it finishes executing before the fetch request is complete. The inner function fetch
closes over the url
function parameter variable. This preserves the url
variable.
Modules
The JavaScript module pattern is a commonly used design pattern in JavaScript to create modules. Modules are useful for code reuse and organization. The module pattern allows functions to encapsulate code like a class does. This means that the functions can have public and private methods and variables. It allows for controlling how different parts of a code base can influence each other. Closures are required for this, for functional modules. Functional modules are immediately invoked function expressions (IIFE). The IIFE creates a closure that has methods and variables that can only be accessed within the function, they are private. To make methods or variables public, they can be returned from the module function. Closures are useful in modules because they allow module methods to be associated with data in their lexical environment (outer scope), the variables in the module:
var myModule = (function () {
var privateVar = 1;
var publicVar = 12345;
function privateMethod() {
console.log(privateVar);
}
function publicMethod() {
publicVar += 1;
console.log(publicVar);
}
return {
publicMethod: publicMethod,
publicVar: publicVar,
alterPrivateVarWithPublicMethod: function() {
return privateVar += 2;
},
};
})();
console.log(myModule.publicVar); // 12345
console.log(myModule.alterPrivateVarWithPublicMethod()); // 3
myModule.publicMethod(); // 12346
console.log(myModule.alterPrivateVarWithPublicMethod()); // 5
console.log(myModule.privateVar); // undefined
myModule.privateMethod(); // Uncaught TypeError: myModule.privateMethod is not a function
Functional programming - currying and composition
Currying a function is when a function that takes multiple arguments is written in such a way that it can only take one argument at a time. It returns a function that takes the next argument, which returns a function that takes the next argument, ... this continues until all of the arguments are provided and then it returns value. It allows you to break up a large function into smaller functions that each handle specific tasks. This can make functions easier to test. Here is an example of a curried function that adds three values together:
function curryFunction(a) {
return (b) => {
return (c) => {
return a + b + c;
};
};
}
console.log(curryFunction(1)(2)(3)); // 6
Composition is when functions are combined to create larger functions, it is an important part of functional programming. Curried functions can be composed into large, complex functions. Composition can make code more readable because of descriptive function names. The following is a simple example of currying and composition where there are two number functions (for simplicity): five
and six
that use the n
function, which allows them to be called alone or composed with other functions such as the plus
function. The isEqualTo
function checks if two numbers are the same.
var n = function (digit) {
return function (operator) {
return operator ? operator(digit) : digit;
};
};
var five = n(5);
var six = n(6);
function plus(prev) {
console.log('prev = ', prev); // prev = 6
return function (curr) {
return prev + curr;
};
}
function isEqualTo(comparator) {
console.log('comparator = ', comparator); // comparator = 5
return function (value) {
return value === comparator;
};
}
console.log(five()); // 5
// values calculated from the inside to the outside
// 1. six() => result1
// 2. plus(result1) => result2
// 3. five(result2) => final result
console.log(five(plus(six()))); // 11
console.log(isEqualTo(five())("5")); // false
You can read more about currying and composition in this article: How to use Currying and Composition in JavaScript.
Here is an example of a debounce function, from https://www.joshwcomeau.com/snippets/javascript/debounce/, that returns a function and makes use of a closure, like the counter example that we used earlier:
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
};
Modern front end frameworks/libraries like React make use of a composition model where small components can be combined to build complex components.
React
Making hooks
Here is a function that mimics the useState
hook. The initial value, the state getter, is enclosed in the closure and acts like stored state:
function useState(initial) {
let str = initial;
return [
// why is the state value a function? No re-render in vanilla JavaScript like in React.
// if you just use the value (no function), then change it with the setter function(setState) and then the log value, it will reference a "stale" value (stale closure) -> the initial value not the changed value
() => str,
(value) => {
str = value;
},
];
}
const [state1, setState1] = useState("hello");
const [state2, setState2] = useState("Bob");
console.log(state1()); // hello
console.log(state2()); // Bob
setState1("goodbye");
console.log(state1()); // goodbye
console.log(state2()); // Bob
To see a better implementation where the state value is not a function check out the following article - Getting Closure on React Hooks.
Closures remember the values of variables from previous renders - this can help prevent async bugs
In React, if you have an async function that relies on props that may change during the async function execution, you can easily end up with bugs if you use class components due to the props value changing. Closures in React functional components make it easier to avoid these types of bugs. Async functions, that use prop values, use closures to preserve the prop values at the time when the function was created. Each time a component renders, a new props object is created. Functions in the component are re-created. Any async functions that use variables from the props (or elsewhere), remember the variables due to closure. If the component that an async function is in is re-rendered and the props change (new values) during the async function call, the async function call will still reference the props from the previous render, where the function was defined, as the values were preserved due to closure. You can see an example of this in the article - How React uses Closures to Avoid Bugs.
Conclusion
We learned what closures are using some examples and saw some example use cases in JavaScript and React. To learn more about closures, you can check the articles linked below.
References / Further Reading
- MDN Closures article
- You Don't Know JS book - Getting Started - Chapter 3
- You Don't Know JS book - Getting Started - Appendix B
- Dan Abramov Closure article
- JavaScript Module Pattern Basics
- Module Design Pattern in JavaScript
- How to use Currying and Composition in React
- Getting Closure on React Hooks
- How React uses Closures to Avoid Bugs
Posted on May 9, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 11, 2024