"Exploring JavaScript Closures: A Practical Guide with a Counter Example"
src/script.js
function createCounter() {
let count = 0;
function increment() {
return ++count;
}
function decrement() {
return --count;
}
function getCount() {
return count;
}
return {
increment,
decrement,
getCount
};
}
const counter = createCounter();
const countDisplay = document.getElementById('count');
const incrementBtn = document.getElementById('incrementBtn');
const decrementBtn = document.getElementById('decrementBtn');
function updateCountDisplay() {
countDisplay.textContent = counter.getCount();
}
incrementBtn.addEventListener('click', () => {
counter.increment();
updateCountDisplay();
});
decrementBtn.addEventListener('click', () => {
counter.decrement();
updateCountDisplay();
});
updateCountDisplay();
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Counter App</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="counter">
<h1 id="count">0</h1>
<button id="incrementBtn">Increment</button>
<button id="decrementBtn">Decrement</button>
</div>
<script src="script.js"></script>
</body>
</html>
src/index.css
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.counter {
text-align: center;
}
#count {
font-size: 3rem;
}
button {
font-size: 1rem;
padding: 0.5rem 1rem;
margin: 0 0.5rem;
cursor: pointer;
}
Code explanation
Closure Creation: When
createCounter()
is called, it creates a lexical scope. Inside this scope, variables likecount
,increment
,decrement
, andgetCount
are declared. These inner functions (increment
,decrement
,getCount
) have access to the outer scope, which includes thecount
variable. This forms a closure, meaning that even after thecreateCounter
function has finished executing, the inner functions still have access to the variables in their lexical scope.Encapsulation: The
createCounter
function encapsulates thecount
variable and the functions that operate on it (increment
,decrement
,getCount
). This encapsulation ensures that thecount
variable cannot be directly accessed or modified from outside thecreateCounter
function, providing data protection and preventing unintended manipulation.Accessing the Counter: The returned object
{ increment, decrement, getCount }
exposes only the necessary functionalities to interact with the counter. This means that thecount
variable is accessible only through the provided methods (increment
,decrement
,getCount
), enforcing encapsulation and preventing direct manipulation of the counter's state.Event Listeners: The event listeners attached to the
incrementBtn
anddecrementBtn
buttons invoke theincrement
anddecrement
methods respectively when clicked. These methods modify thecount
variable, which is maintained via closures, and then callupdateCountDisplay
to reflect the updated count in the DOM.Updating DOM: The
updateCountDisplay
function retrieves the current count using thegetCount
method and updates the content of thecountDisplay
element accordingly. This function is called initially to display the initial count, and subsequently after each increment or decrement operation.
In summary, closures in JavaScript allow inner functions to maintain access to the variables of their outer scope even after the outer function has finished executing. This property is utilized in the provided code to encapsulate and maintain the state of the counter, ensuring data integrity and providing a clean interface for interacting with the counter functionality.