Understanding JavaScript Closures: A Detailed Guide with Examples ??
Sonu Tiwari
Crafting Stunning UI/UX for a Billion Users Across Demographics | Let’s Connect!
Closures are one of the most powerful and unique features in JavaScript. They can be a bit tricky to understand at first, but once you grasp the concept, they open up many possibilities in writing efficient and flexible code. Let’s break it down step by step in simple language, and include real-world examples for better clarity.
1. What Are Closures?
In JavaScript, closures happen when a function “remembers” its outer environment (the variables and functions outside it) even after it’s executed. This means that even when the outer function has finished running, the inner function can still access the variables defined in the outer function.
Simple Definition:
A closure is created when a function can access variables from its outer scope even after that outer function has finished running.
2. How Do Closures Work?
To explain closures, we need to understand the scope first.
? Global Scope: Variables available everywhere in your JavaScript code.
? Local Scope (Function Scope): Variables inside a function are only available within that function.
? Lexical Scope: This refers to how variables are scoped. JavaScript functions can access variables from their outer (or parent) scope.
A closure happens when an inner function remembers the variables from its outer scope. Let’s see this in code:
Basic Example of a Closure:
function outerFunction() {
let outerVar = "I am from the outer function";
function innerFunction() {
console.log(outerVar); // innerFunction can access outerVar
}
return innerFunction;
}
const closure = outerFunction();
closure(); // Output: I am from the outer function
Explanation:
? outerFunction creates a local variable outerVar and returns the innerFunction.
? The innerFunction can still access outerVar even after outerFunction has finished running, creating a closure.
3. Real-world use Cases of Closures in JavaScript
Closures are commonly used in many practical scenarios:
3.1. Data Privacy (Encapsulation)
You can use closures to hide data and make it private. For example, if you want some data or state to be only accessible through specific functions, you can use closures to do that.
function bankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
balance += amount;
console.log(`Deposited: ${amount}, New Balance: ${balance}`);
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`Withdrew: ${amount}, New Balance: ${balance}`);
} else {
console.log('Insufficient balance!');
}
}
};
}
const myAccount = bankAccount(1000);
myAccount.deposit(500); // Deposited: 500, New Balance: 1500
myAccount.withdraw(200); // Withdrew: 200, New Balance: 1300
Explanation:
? The balance variable is private. You can only modify it using the deposit and withdraw methods. This protects balance from direct changes, which is a great use of closures.
3.2. Function Factories
Closures can help create function factories where you generate a new function based on input.
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2); // Creates a function to multiply by 2
console.log(double(5)); // Output: 10
const triple = multiplier(3); // Creates a function to multiply by 3
console.log(triple(5)); // Output: 15
Explanation:
? The multiplier function creates a new function every time with its own enclosed factor. Each of those functions remembers the value of factor from the time they were created, making them closures.
3.3. Callback Functions and Event Listeners
Closures are used heavily in callbacks and event listeners, especially in asynchronous programming
function clickHandler() {
let count = 0;
return function() {
count++;
console.log(`Button clicked ${count} times`);
};
}
const button = document.querySelector('button');
button.addEventListener('click', clickHandler());
Explanation:
? Each time the button is clicked, the inner function remembers the count variable due to the closure, and increments it.
4. Examples of Tricky Closure Scenarios
Closures can sometimes cause unexpected behavior, especially with loops and asynchronous code.
4.1. Closure in a Loop
One common mistake is when closures in loops don’t work as expected:
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
// Output: 6 6 6 6 6 (after each second)
Explanation:
? Here, var i is hoisted and shared across all iterations. By the time the setTimeout callback is executed, i has become 6, which is why it logs 6 five times.
Fix Using let:
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
// Output: 1 2 3 4 5 (each after one second)
Explanation:
? Using let creates a new block scope for each iteration, so each setTimeout gets its own value of i.
4.2. Closure and Global Variables
Closures can also access global variables, but it’s often better to avoid relying on them for maintainability.
let globalVar = "I'm global";
function accessGlobal() {
console.log(globalVar); // Accesses the global variable
}
accessGlobal(); // Output: I'm global
5. Closures with IIFE (Immediately Invoked Function Expressions)
IIFE (Immediately Invoked Function Expressions) are functions that are executed right after they are created. Closures in IIFE allow us to create private variables easily.
let increment = (function() {
let counter = 0;
return function() {
counter++;
console.log(counter);
};
})();
increment(); // Output: 1
increment(); // Output: 2
Explanation:
? The counter variable is private within the IIFE and can only be modified through the returned function, showcasing a closure in action.
6. Function Execution Context and Closures
Closures are created due to JavaScript’s execution context and lexical environment. When a function is called, it creates a new execution context with its own local variables, but closures allow inner functions to access variables from their outer environment.
Conclusion
Closures in JavaScript give us powerful tools to:
? Preserve data over time (like counters or state).
? Encapsulate and hide variables (for privacy).
? Create reusable functions with customized behavior (function factories).
? Handle asynchronous actions (callbacks and event listeners).
Once you get comfortable with closures, you’ll find them extremely useful in building efficient, clean, and flexible JavaScript applications!
You can explore these examples in your own projects if you like. Once closures “click” for you, they will become one of your favourite tools in JavaScript! ??