In JavaScript, a closure is a functionality that allows functions to have access to the scope of the function that contains them, even after this outer function has finished executing.
In other words, a closure “closes” the scope of the function that created it, allowing the local variables of that function to remain accessible.
Closures are an automatic functionality. JavaScript manages closures for us when needed (meaning we don’t have to do anything).
However, it is interesting to understand how they work both to avoid unexpected errors and because it has implications for JavaScript’s memory management.
How closures are created
As I said, closures are created automatically. This happens when an inner function is defined inside another function.
The closure allows the inner function to have access to the variables of the outer function, even after the outer function has finished its execution.
Let’s see it 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:
createCounteris a function that defines a local variablecountand returns an anonymous function- The inner function has access to the variable
countfrom its outer function (createCounter) - The outer function
createCounterhas already finished executing. So we might think that the variablecountno longer exists - However, the counter keeps working 🤔
- This is because JavaScript has created a closure, so
countcontinues to exist
How closures work
To understand how closures work, it’s important to understand how JavaScript handles scope and memory.
Lexical Scope
JavaScript uses the concept of lexical scope, which means that a function’s scope is determined at the moment of its creation, not at the moment 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 Chains
When a variable is accessed within a function, JavaScript follows a scope chain.
First, it looks for the variable in the local scope. If not found, it looks in the scope of the outer function, 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 maintains references to variables that are no longer needed, it can cause these variables not to 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 freed.
Practical examples
Callback Functions
Closures are essential in the use of 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 inside
setTimeoutis a closure - It remembers the value of the variable
messageeven after thewaitfunction has finished executing.
Use 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.
