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 !❤️