Practical usecases of Closures in JavaScript
A closure references variables in the outer scope from its inner scope This allows a function to use those variables within its scope, preserving the context of the outer scope within its inner scope.
First, let's use an analogy to understand what closures are
Analogy
Imagine you have a treasure box with a special lock that needs a secret key to open it. It is possible to make a copy of this key using special superpowers. Your superpowers keep a copy of this key even after you have closed the lock absence of a key. This means you can use the superpowers to create a secret key to open the box.
The treasure box is like a function and the lock and key are like variables inside a function. The superpowers are like a closure. A closure allows a function to remember the variables even after the function has finished executing, if the function is invoked again it can still access and use those variables.
What are closures?
Let's delve into how closures are used in JavaScript
Closures are like special powers that functions have. Imagine you have a function that creates another function. This inner function can still access and use variables from the outer function, even after the outer function has finished executing. This is possible because of closures.
function outerScope() {
const name = "John Doe"; // name is a local variable created by outerScoper
function innerScope() {
// innerScope() is the inner function, that forms the closure
console.log(`Hello ${name}`); // use variable declared in the parent function
}
innerScope();
}
outerScope();
Let's break the above example further:
- Outer Scope Function: This function, serves as the parent scope. Within this function, a local variable called
name
is declared, along with another function calledinnerScope
. This function is defined within theouterScope
function, making it an inner function. - Inner Scope Function: The
innerScope
function is defined within theouterScope
function. Despite being defined within, theinnerScope
function is only accessible within theouterScope
function's lexical scope. It cannot be accessed from outsideouterScope
. - Closure: The
innerScope
function has access to the variables declared in its parent scope, which is theouterScope
function. TheinnerScope
function does not have its local variables, it can access and manipulate thename
variable declared in theouterScope
function. This behavior is possible due to closures in JavaScript. - Lifetime: Even after the
outerScope
function has finished executing, theinnerScope
function still retains access to thename
variable and can continue using it.
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
Application of closures
- Functional programming
Closures play an integral role in the implementation of higher-order functions. Higher-order functions take other functions as arguments or return functions as the output. By preserving the behavior of functions within closures, HOF can be created to implement patterns such as map, filter, and reduce enabling developers to write readable code.
// Function to create a multiplier function
function createMultiplier(factor) {
// This inner function (closure) retains access to the 'factor' variable
return function(x) {
return x * factor;
};
}
// Create multiplier functions for different factors
const double = createMultiplier(2);
const triple = createMultiplier(3);
// Use the multiplier functions
console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15
- Data Privacy
Closures enable the creation of private variables and functions within a function’s scope. Developers can create modules and libraries with hidden implementation details by encapsulating data within closures. This prevents external code from accessing or modifying internal state directly and promotes information hiding thus enhancing data protection.
function createCounter() {
let count = 0; // This variable is private to the createCounter function
return {
increment: function() {
count++;
},
decrement: function() {
count--;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.getCount()); // Output: 0
counter.increment();
counter.increment();
console.log(counter.getCount()); // Output: 2
counter.decrement();
console.log(counter.getCount()); // Output: 1
In this example, the createCounter
function returns an object with three methods: decrement, increment
, and getCount
. These methods are closures that access to the count
variable declared in the outer createCounter
function. However, the count
variable is not accessible from outside the createCounter
scope, making it private to the returned object.
- Event handlers
Closures work with event handlers in JavaScript by allowing inner functions defined within event handlers to retain access to variables from their outer lexical scope. This enables event handlers to maintain context and access relevant data after the outer function has finished running.
- Defining Event Handlers: By defining an event handler such as a function to handle form submit, you are creating a callback function that will be executed when an event occurs
- Accessing Variables from Outer Scope: Inside the event handler function, you can access variables defined in the parent function due to closures. The inner function retains access to the variables i.e. it is closed over even after the outer function has been executed.
function handleCounter() {
let count = 0;
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
count++;
console.log('Button clicked ' + count + ' times');
});
}
handleCounter();
In this example, the event handler function (defined inside addEventListener
) has access to the count
variable from its outer lexical scope (handleCounter function). Each time the button is clicked the count variable is incremented and logged to the console even after the handleCounter function has finished executing.