One of the few challenges that beginners face while learning JavaScript is the concept of closures. Although fairly simple, closures are one of the more confusing concepts to understand, especially when one tries to implement it in a project. In this post, we'll try to understand what closures are in the simplest terms possible along with some practical examples. Let's begin!
Pre-requisites
Basic JavaScript
Functions
IIFE's (Immediately Invoked Function Expressions)
Before diving into closures, it's essential to have a good grasp of basic JavaScript concepts, including functions and Immediately Invoked Function Expressions (IIFE's). If you're not familiar with these topics (or even if you know them), I recommend brushing up on them before proceeding.
What are Closures?
Let's start with a non-technical analogy: Imagine a closure as a little function that carries a memory of its past. It can still use the stuff it learned from where it was born, even when it’s out exploring the world.
Now for the technical explanation: At its core, a closure is simply a function that remembers its outer scope even when the function is executed outside that scope. In other words, a closure allows a function to access variables from an enclosing function scope even after the outer function has finished executing.
Let's break this down with an example:
function outerFunction() {
let outerVariable = "I am from outerFunction";
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
let myFunction = outerFunction();
myFunction(); // Output: "I am from outerFunction"
In this example, innerFunction
is a closure. Even though outerFunction
has finished executing, innerFunction
still has access to the outerVariable
from its lexical scope.
Practical Use Cases
Closures are commonly used in JavaScript for maintaining state, data privacy, and creating higher-order functions.
Maintaining State:
function counter() { let count = 0; return function() { return ++count; }; } let incrementCounter = counter(); console.log(incrementCounter()); // Output: 1 console.log(incrementCounter()); // Output: 2
In this example, the
counter
function returns a closure that increments thecount
variable each time it's called, effectively maintaining the state across multiple function calls.Data Privacy:
function createPerson(name) { return { getName: function() { return name; }, setName: function(newName) { name = newName; } }; } let person = createPerson("Alice"); console.log(person.getName()); // Output: "Alice" person.setName("Bob"); console.log(person.getName()); // Output: "Bob"
In this example, the
createPerson
function returns an object withgetName
andsetName
methods. These methods form a closure over thename
variable, providing data privacy by allowing controlled access to the variable.Creating Higher-order Functions:
function multiplier(x) { return function(y) { return x * y; }; } let multiplyByTwo = multiplier(2); console.log(multiplyByTwo(3)); // Output: 6
Here, the
multiplier
function returns a closure that takes ay
argument and multiplies it byx
, demonstrating how closures can be used to create higher-order functions.
Conclusion
Let's summarize whatever you've read above:
A closure is a special kind of function.
When you create a closure, it remembers the variables from the place where it was defined.
Even if you use the closure somewhere else, it still has access to those remembered variables.
If, while reading this article, you come across some terms or concepts that you're not aware of (for example "state", or "Higher-order functions"), I encourage you to look them up in short, come back, and read it again. This way you'll have a better understanding of the concepts. Happy learning!