Closures in JavaScript
A closures is a function that retains access to its lexical scope, even when it is executed outside of its original scope.
Closure remembers the varibales from the scope in which it was created, and it allowing to persist data across multiple function calls.
How Closures Work
When a function is returned from another function, it keeps a reference to the variables in its lexical scope instead of copying them. This allows it to retain data even after the other function has completed execution.
Example:
function Counter(){
let counter = 0;
return function increaseCounter(){
counter++;
console.log(`Counter : ${counter}`)
}
}
const count = Counter();
count(); // Counter : 1
count(); // Counter : 2
count(); // Counter : 3
count(); // Counter : 4
Even though Counter()
has finished execution, the returned function increaseCounter()
still has access to counter because of closures.
Commonly Used Examples of Closures
-
Data Encapsulation
Closures allow us to create private variables that cannot be accessed directly from outside a function.
function createBankAccount(initialBalance) { let balance = initialBalance; return { deposit(amount) { balance += amount; console.log(`Deposited: ${amount}, New Balance: ${balance}`); }, withdraw(amount) { if (amount > balance) { console.log("Insufficient funds"); return; } balance -= amount; console.log(`Withdrew: ${amount}, Remaining Balance: ${balance}`); } }; } const myAccount = createBankAccount(100); myAccount.deposit(50); // Deposited: 50, New Balance: 150 myAccount.withdraw(30); // Withdrew: 30, Remaining Balance: 120
Here,
balance
is private and cannot be modified directly from outside.
-
Function Factories
Closures allow us to create dynamic functions with preset values.
function multiplier(factor) { return function(number) { return number * factor; }; } const double = multiplier(2); // take factor arg const triple = multiplier(3); // take factor arg console.log(double(5)); // take number arg // Output: 10 console.log(triple(5)); // take number arg // Output: 15
Here,
double
andtriple
are functions that remember theirfactor
values.
-
Memoization
Closures helps to store previously computed results to improve performance. It used in caching the function results.
function memoize() { let cache = {}; return function(n) { if (n in cache) { console.log("Fetching from cache"); return cache[n]; } else { console.log("Calculating result"); let result = n * n; cache[n] = result; return result; } }; } const square = memoize(); console.log(square(5)); // Calculating result, Output: 25 console.log(square(5)); // Fetching from cache, Output: 25
-
Event Handlers in JavaScript
Closures help manage UI state in event handlers.
function attachEventHandlers() { let count = 0; document.getElementById("button").addEventListener("click", function() { count++; console.log(`Clicked ${count} times`); }); } attachEventHandlers();
Preventing Memory Issues with Closures
While Closures are powerful, they can sometimes cause memory leaks. As we know closures keep a reference to thier outer function's scope, variables may not be freed up if they are still accessible.
Preventing Memory Issues with Closures
- If a closure is no longer needed, setting variables to
null
can help release memory. - We can use
WeakMap
orWeakSet
for storing large objects, becauseWeakMap
andWeakSet
hold "weak" references, which means if an object is no longer referenced anywhere else, it will be automatically garbage collected.