DEV Community

Cover image for Understanding Closures in JavaScript with Simple Practical Examples
Alex for OpenSign

Posted on

Understanding Closures in JavaScript with Simple Practical Examples

Closures are a fundamental yet powerful feature of JavaScript. They are often one of the most misunderstood concepts, specially among beginners. If you are looking to deepen your understanding of closures and learn how to use them in real world situations this article is for you.

What Are Closures?
In JavaScript a closure is a unique function that remembers the environment in which it was created. What it means is that it can access variables from its outer scope even after the outer function has finished the execution.

Here is a simple example to illustrate the concept of closures:

function greet() {
  const name = 'Alex';
  function displayName() {
    alert('Hello, ' + name);
  }
  return displayName;
}

const myGreet = greet();
myGreet(); // Alerts "Hello, Alex"
Enter fullscreen mode Exit fullscreen mode

In the code shown above displayName is a closure that encloses both the function itself and the environment in which it was created which includes the variable name.

Why Use Closures?
Closures are useful for many reasons:

  • Closures allow you to emulate private variables which provides security and prevents the global namespace from being cluttered.
  • Closures enable powerful functional programming techniques.
  • Closures maintain state in an asynchronous javascript world.

Here are some Practical Use Cases:

  1. Data Encapsulation and Privacy:
   function createCounter() {
     let count = 0;
     return {
       increment() { count += 1; },
       get value() { return count; }
     };
   }

   const counter = createCounter();
   counter.increment();
   console.log(counter.value); // 1
Enter fullscreen mode Exit fullscreen mode

The count variable in above code is private. It is not accessible outside the scope of createCounter other than through the closure's methods.

  1. Event Handlers:
   function setupButton(buttonId) {
     const button = document.getElementById(buttonId);
     button.onclick = function() {
       alert('Button ' + buttonId + ' clicked');
     };
   }
   setupButton('myButton');
Enter fullscreen mode Exit fullscreen mode

Each button click handler created by setupButton in above code is a closure with access to its very own buttonId variable.

  1. Partial Applications or Currying:
   function multiply(a, b) {
     return a * b;
   }

   function createMultiplier(multiplier) {
     return function(x) {
       return multiply(x, multiplier);
     };
   }

   const double = createMultiplier(2);
   console.log(double(3)); // 6
Enter fullscreen mode Exit fullscreen mode

createMultiplier returns a closure that multiplies its argument by a fixed multiplier.

Understanding the Nuances:
Closures come with their own set of potential pitfalls such as the loop problem:

for (var myi = 1; myi <= 5; myi++) {
  setTimeout(function() {
    console.log('myi: ' + myi);
  }, myi * 2000);
}
// Outputs "myi: 6" five times
Enter fullscreen mode Exit fullscreen mode

You might expect the above code to log the numbers 1 to 5 after every two seconds gap. However it will actually print the number 6 five times two seconds apart instead. This happens because the setTimeout functions are executed after the loop has completed execution at which point the variable โ€˜myiโ€™ has a value of 6.

To fix this using closures you can create a new scope by adding an Immediately Invoked Function Expression(IIFE) like below

for (var myi = 1; myi <= 5; myi++) {
  (function(myj) {
    setTimeout(function() {
      console.log('myj: ' + myj);
    }, myj * 1000);
  })(myi);
}
Enter fullscreen mode Exit fullscreen mode

We will not go deep into the above solution as we have a better solution for this problem in the next section. For now the quick explanation is -

myj is a parameter of the IIFE and myi is passed to it. As function parameters are local to the function this myj variable is different for each iteration which implies that setTimeout callback refers to the correct myj value when itโ€™s called.

The modern day solution to this particular issue is to simply use let instead of var. As we already know that let is block-scoped and creates a new binding for each iteration

for (let myi = 1; myi <= 5; myi++) {
  setTimeout(function() {
    console.log(myi);
  }, myi * 1000);
}
Enter fullscreen mode Exit fullscreen mode

With the help of let each loop iteration now creates a new myi variable scoped to that iteration. That means the setTimeout callback will work with the myi that is available at the time of the iteration and gives you the expected output.

Closures should not be seen as just a theoretical concept they are a practical tool that can solve real-world problems in JavaScript world. By understanding and applying closures correctly, you can write more efficient and extremely secure code. As you dive into open-source projects like OpenSign, you'll encounter closures frequently. Understanding them will help you contribute more effectively. Visit OpenSign on GitHub to start your open source journey.

Now that you have a better grasp of closures, try implementing them in your next project or while contributing to an open-source initiative. Share your experiences or any questions in the comments below!

โญ OpenSign on GitHub

Top comments (3)

Collapse
 
jonrandy profile image
Jon Randy ๐ŸŽ–๏ธ

In JavaScript a closure is a unique function that remembers the environment in which it was created.

Unfortunately this is not correct, for two reasons:

  • Closures are not functions
  • ALL functions remember the environment in which they were created
Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Nice article on closures. Always useful in JavaScript when you need to reference something outside of normal scope.

Collapse
 
arindam_1729 profile image
Arindam Majumder

Great Share