"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;
  }

This is output screen

Code explanation

  1. Closure Creation: When createCounter() is called, it creates a lexical scope. Inside this scope, variables like count, increment, decrement, and getCount are declared. These inner functions (increment, decrement, getCount) have access to the outer scope, which includes the count variable. This forms a closure, meaning that even after the createCounter function has finished executing, the inner functions still have access to the variables in their lexical scope.

  2. Encapsulation: The createCounter function encapsulates the count variable and the functions that operate on it (increment, decrement, getCount). This encapsulation ensures that the count variable cannot be directly accessed or modified from outside the createCounter function, providing data protection and preventing unintended manipulation.

  3. Accessing the Counter: The returned object { increment, decrement, getCount } exposes only the necessary functionalities to interact with the counter. This means that the count variable is accessible only through the provided methods (increment, decrement, getCount), enforcing encapsulation and preventing direct manipulation of the counter's state.

  4. Event Listeners: The event listeners attached to the incrementBtn and decrementBtn buttons invoke the increment and decrement methods respectively when clicked. These methods modify the count variable, which is maintained via closures, and then call updateCountDisplay to reflect the updated count in the DOM.

  5. Updating DOM: The updateCountDisplay function retrieves the current count using the getCount method and updates the content of the countDisplay 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.