JavaScript
closures are a powerful and fundamental concept in the language. They occur when a function retains access to variables from its containing (enclosing) lexical scope even after that scope has finished executing. Closures enable a wide range of programming techniques and are essential for creating modular and private code. In this explanation, we'll explore closures through code examples.
Lexical Scope
First, it's important to understand lexical scope. In JavaScript
, the scope of a variable is determined by its location within the code. Variables declared in an outer (enclosing) function can be accessed by inner functions within that outer function.
Let's begin with a simple example:
function outer() {
const outerVar = "I'm from outer!";
function inner() {
console.log(outerVar);
}
return inner;
}
const innerFunc = outer();
innerFunc(); // Outputs: "I'm from outer!"
In this example, outer defines a variable outerVar
, and inner is an inner function that logs the value of outerVar
. When we call innerFunc()
, it still has access to outerVar
even though outer has finished executing. This is the essence of closures.
Closures in Action
Closures
become more useful when we return inner functions from outer functions. These inner functions can then be invoked later, preserving their access to the outer function's variables. Here's an example:
function createCounter() {
let count = 0;
function increment() {
count++;
console.log(count);
}
function decrement() {
count--;
console.log(count);
}
return { increment, decrement };
}
const counter = createCounter();
counter.increment(); // Outputs: 1
counter.increment(); // Outputs: 2
counter.decrement(); // Outputs: 1
In this example, createCounter
defines an inner variable count and two inner functions (increment and decrement)
. When we call createCounter()
, it returns an object with references to these inner functions. This object effectively "closes over" the count variable, creating a closure. We can then use counter to manipulate the count variable, even though createCounter
has already executed.
Encapsulation and Private Variables
Closures enable encapsulation
and the creation of private variables. By exposing only certain functions and variables through the returned object, we can control access to the internal state:
function createPerson(name, age) {
// Private variables
const privateName = name;
let privateAge = age;
// Public methods
return {
getAge: function() {
return privateAge;
},
setAge: function(newAge) {
if (newAge >= 0) {
privateAge = newAge;
}
},
getName: function() {
return privateName;
}
};
}
const person = createPerson("Alice", 30);
console.log(person.getName()); // Outputs: "Alice"
console.log(person.getAge()); // Outputs: 30
person.setAge(25);
console.log(person.getAge()); // Outputs: 25
In this example, createPerson
encapsulates privateName
and privateAge
variables, exposing only a controlled interface to access and modify them.
Common Use Cases
Closures
are employed in various JavaScript patterns and use cases, including:
1. Data Hiding and Encapsulation
Closures can be used to create private variables and methods, providing data hiding and encapsulation.
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // Outputs: 1
In this example, count is encapsulated
within the closure created by createCounter
, and it can only be accessed or modified through the returned object's methods.
2. Callbacks and Event Handlers
Closures
are helpful for callbacks and event handlers to remember their context.
function onClick() {
let count = 0;
return function() {
count++;
console.log(`Button clicked ${count} times.`);
};
}
const buttonClickHandler = onClick();
document.getElementById("myButton").addEventListener("click", buttonClickHandler);
Here, the onClick
function returns a closure that keeps track of the click count. When the button is clicked, the closure is invoked, and it remembers the count variable's state.
3. Partial Application and Currying
Closures
enable partial application and currying, which are useful in functional programming.
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(3)); // Outputs: 8
In this example, the add function takes an argument a and returns a closure that takes another argument b. This allows us to create specialized functions like add5, which adds 5 to its argument.
4. Module Pattern
Closures
can be used to create self-contained modules to prevent variable pollution in the global scope.
const MyModule = (function() {
let privateVar = 0;
function privateFunction() {
console.log("This is a private function.");
}
return {
publicVar: 42,
publicFunction: function() {
console.log("This is a public function.");
}
};
})();
console.log(MyModule.publicVar); // Outputs: 42
MyModule.publicFunction(); // Outputs: "This is a public function"
console.log(MyModule.privateVar); // Outputs: undefined (private)
MyModule.privateFunction(); // Outputs: TypeError (private)
In this example, MyModule
is a self-contained module where privateVar
and privateFunction
are hidden from the global scope, and only the public interface is accessible.
In conclusion, closures
are a crucial concept in JavaScript, allowing functions to retain access to their enclosing scope's variables. They enable encapsulation
, data hiding
, and a range of advanced programming techniques, making them an essential tool in JavaScript development.
LinkedIn Account
: LinkedIn
Twitter Account
: Twitter
Credit: Graphics sourced from medium
Top comments (0)