In JavaScript, a closure (or closure) is a feature that allows functions to have access to the scope of the function that contains them, even after that external function has finished executing.
In other words, a closure “closes over” the scope of the function that created it, allowing the local variables of that function to remain accessible.
Closures are an automatic feature. JavaScript manages closures for us when necessary (i.e., we don’t have to do anything).
However, it is interesting to understand how they work both to avoid unexpected errors and because they have implications for JavaScript’s memory management.
Creating closures
As I mentioned, closures are created automatically. This occurs when an inner function is defined within another function.
The closure allows the inner function to access the variables of the outer function, even after the outer function has completed its execution.
Let’s see this better with an example:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
In this example:
createCounter
is a function that defines a local variablecount
and returns an anonymous function- The inner function has access to the variable
count
from its outer function (createCounter
) - The outer function
createCounter
has already finished executing. So we might think that the variablecount
ceases to exist - However, the counter continues to work 🤔
- This is because JavaScript has created a closure, so
count
continues to exist
How closures work
To understand how closures work, it is important to understand how JavaScript handles scope and memory.
Lexical Scope
JavaScript uses the concept of lexical scope, meaning that the scope of a function is determined at the time of its creation, not at the time of its execution.
When a closure is created, the inner function “remembers” the lexical environment in which it was created, which includes all the variables and parameters of the outer function.
Scope Chain
When accessing a variable in a function, JavaScript follows a scope chain.
First, it looks for the variable in the local scope. If it’s not found, it searches in the outer function’s scope, and so on, until it reaches the global scope.
Closures preserve this scope chain, allowing access to the variables of the outer function.
Precautions when using closures
Although closures are useful and have many advantages, they can also lead to higher memory consumption (if not handled properly).
If a closure holds references to variables that are no longer needed, it may cause those variables to not be collected by the garbage collector, resulting in memory leaks.
Free Resources: When closures are no longer needed, make sure they do not hold references to resources that can be released.
Practical examples
Callback Functions
Closures are essential for using callback functions and promises, as they allow access to the context where they were created.
function wait(ms) {
let message = 'Wait completed';
setTimeout(function() {
console.log(message);
}, ms);
}
wait(2000); // "Wait completed" is printed after 2 seconds
In this example,
- The inner function of
setTimeout
is a closure - It remembers the value of the variable
message
even after thewait
function has finished executing.
Using with Promises
function processData(data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`Data processed: ${data}`);
}, 1000);
});
}
const data = "Important information";
processData(data).then(console.log); // Output: "Data processed: Important information"
In this example, the callback function inside setTimeout
has access to the variable data
, thanks to closures.
Partial Functions and Currying
Closures are useful for implementing techniques like currying and partial functions, where you can fix certain arguments of a function and create new functions with the remaining arguments.
function multiply(factor) {
return function(number) {
return number * factor;
};
}
const multiplyBy2 = multiply(2);
console.log(multiplyBy2(5)); // Output: 10
Here, multiplyBy2
is a closure that remembers the value 2
as its context, allowing for easy multiplications.