Welcome, JavaScript pathfinders! This chapter delves into the fascinating realm of closures. Closures are a powerful concept that allows functions to "remember" and access variables from their outer (enclosing) scope even after the outer function has finished executing. Buckle up as we explore the fundamentals, practical applications, and advanced techniques of closures in JavaScript.
function outerFunction() {
const name = "Alice";
function innerFunction() {
console.log("Hello from the inner function:", name);
}
return innerFunction; // Closure is formed here
}
const greet = outerFunction(); // outerFunction executes, innerFunction is returned
greet(); // Output: Hello from the inner function: Alice (innerFunction remembers name)
outerFunction defines a variable name.innerFunction is defined within outerFunction.outerFunction returns innerFunction, a closure is formed. Even though outerFunction finishes executing, innerFunction still remembers the value of name from the outer scope because of the closure.greet (which holds the reference to innerFunction), it can still access name and log the greeting message.
function createModule(initialValue) {
let counter = initialValue;
function increment() {
counter++;
}
function decrement() {
counter--;
}
function getCount() {
return counter;
}
return {
increment,
decrement,
getCount,
};
}
const counterModule = createModule(0);
counterModule.increment();
counterModule.increment();
console.log(counterModule.getCount()); // Output: 2 (counter remains private)
createModule function creates a closure. The inner functions (increment, decrement, and getCount) have access to the private variable counter even after createModule finishes.increment and decrement) and a function to get the current count (getCount), but the actual counter value remains hidden within the closure.
const buttons = document.querySelectorAll("button");
buttons.forEach(button => {
button.addEventListener("click", function() {
const buttonText = button.textContent; // Capture button text in closure
function showTextLater() {
console.log("Clicked button with text:", buttonText);
}
setTimeout(showTextLater, 2000); // Schedule showing text after a delay
});
});
buttonText) within a closure.setTimeout schedules the showTextLater function to run later, it still has access to the captured buttonText even though the click event has already happened.
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // Output: 10
console.log(triple(10));
createMultiplier function takes a multiplier and returns a new function.number and returns the product of the number and the captured multiplier from the outer scope.createMultiplier(2), a closure is formed, and the returned function remembers the value of multiplier (2). Assigning it to double, we can multiply by 2.triple = createMultiplier(3) creates a closure with a multiplier of 3.
function fibonacci(n) {
if (n <= 1) {
return n;
}
let cache = {}; // Private cache object within the closure
function calculateFib(n) {
if (cache[n] !== undefined) {
return cache[n];
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
cache[n] = result;
return result;
}
return calculateFib(n);
}
console.log(fibonacci(40)); // Output: 102334155 (faster due to memoization)
fibonacci function calculates the nth Fibonacci number recursively.cache object is defined within the closure using let.calculateFib function checks the cache for the result before recalculating. If the value for n exists in the cache, it’s returned immediately.cache for future calls with the same n. This avoids redundant calculations.Closures are a cornerstone of functional programming in JavaScript. By understanding how functions "remember" variables from their outer scopes, you can create powerful and flexible code with data privacy, event handling with preserved data, function currying for partial application, and memoization for performance optimization. Happy coding !❤️
