JavaScript Closures
A closure is a function that has access to variables from its outer (enclosing) function's scope, even after the outer function has returned. Closures are one of the most important and powerful concepts in JavaScript.
What is a Closure?
A closure is created when a function is defined inside another function. The inner function 'closes over' the variables of the outer function, retaining access to them even after the outer function finishes executing.
function outer() {
const message = 'Hello from outer!';
function inner() {
// inner has access to 'message' from outer's scope
console.log(message);
}
return inner;
}
const myFunc = outer();
// outer() has finished, but inner still has access to 'message'
myFunc();
myFunc();Lexical Scope
Closures rely on lexical scoping, which means a function's scope is determined by where it is defined in the source code, not where it is called. Inner functions can access variables from all outer scopes.
function grandparent() {
const a = 'grandparent';
function parent() {
const b = 'parent';
function child() {
const c = 'child';
// Has access to all outer scopes
console.log(a + ' > ' + b + ' > ' + c);
}
child();
}
parent();
}
grandparent();Function Returning Function
The most common closure pattern is a function that returns another function. The returned function retains access to the variables of the enclosing function.
function multiplier(factor) {
// 'factor' is closed over by the returned function
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
const times10 = multiplier(10);
console.log('double(5): ' + double(5));
console.log('triple(5): ' + triple(5));
console.log('times10(5): ' + times10(5));
console.log('double(7): ' + double(7));Private Variables
Closures can create private variables that are not accessible from outside the function. This is a powerful pattern for data encapsulation and information hiding.
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private variable
return {
deposit: function(amount) {
balance += amount;
console.log('Deposited: $' + amount);
},
withdraw: function(amount) {
if (amount > balance) {
console.log('Insufficient funds!');
return;
}
balance -= amount;
console.log('Withdrawn: $' + amount);
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log('Balance: $' + account.getBalance());
// balance is not accessible directly
console.log('Direct access: ' + typeof account.balance);Counter Example
A classic closure example is a counter function. Each call to the factory creates an independent counter with its own private state.
function createCounter(start) {
let count = start || 0;
return {
increment: function() { return ++count; },
decrement: function() { return --count; },
getCount: function() { return count; },
reset: function() { count = start || 0; return count; }
};
}
const counter1 = createCounter(0);
const counter2 = createCounter(10);
console.log('C1: ' + counter1.increment());
console.log('C1: ' + counter1.increment());
console.log('C1: ' + counter1.increment());
console.log('C2: ' + counter2.increment());
console.log('C2: ' + counter2.decrement());
console.log('C1 count: ' + counter1.getCount());
console.log('C2 count: ' + counter2.getCount());
// Each counter is independent!Practical Uses and Memory Considerations
Closures are used extensively in JavaScript for event handlers, callbacks, module patterns, and partial application. However, closures keep their outer variables in memory, which can lead to memory leaks if not managed carefully.
// Closure in setTimeout
function delayedGreeting(name) {
setTimeout(function() {
console.log('Hello, ' + name + '!');
}, 100);
}
delayedGreeting('Alice');
delayedGreeting('Bob');
// Closure for partial application
function logger(prefix) {
return function(message) {
console.log('[' + prefix + '] ' + message);
};
}
const infoLog = logger('INFO');
const errorLog = logger('ERROR');
infoLog('Application started');
errorLog('Something went wrong');
infoLog('Request processed');| Use Case | Description |
|---|---|
| Data privacy | Create private variables inaccessible from outside |
| Function factories | Create functions with pre-configured behavior |
| Event handlers | Maintain state in callback functions |
| Module pattern | Encapsulate related functionality |
| Partial application | Pre-fill some arguments of a function |
| Memoization | Cache computed results |