JavaScript Closures: When Functions Have Trust Issues

Eftee Codes
5 min readOct 23, 2024

--

JavaScript closures are one of the most powerful features of the language, but they can also be one of the more difficult concepts to fully grasp. Closures enable functions to retain access to their lexical environment, even when that function is executed outside of its original scope. This capability is what gives closures their unique power in JavaScript.

In this blog, we’ll explore closures in detail: what they are, how they work, and how you can use them effectively. By the end, you should have a solid understanding of closures and their applications in your code.

What is a Closure? 💡

A closure is a combination of a function and the lexical environment within which that function was declared. The lexical environment refers to the variables that were in scope at the time the function was created. Even after the outer function has finished execution, the inner function retains access to these variables.

Here’s a simple example:

function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer Variable: ${outerVariable}`);
console.log(`Inner Variable: ${innerVariable}`);
};
}

const newFunction = outerFunction("I'm from the outer function!");
newFunction("I'm from the inner function!");

Output:

Outer Variable: I'm from the outer function!
Inner Variable: I'm from the inner function!

Explanation:

  • outerFunction defines a local variable outerVariable and returns the innerFunction.
  • innerFunction, when called, still has access to outerVariable even though outerFunction has completed its execution. This is because of closureinnerFunction retains access to the scope in which it was defined, including all the variables that were in that scope at the time.

How Closures Work: A Step-by-Step Breakdown 🔍

  1. Lexical Scope: When a function is declared, it captures references to the variables in its lexical scope. This means any variable defined in its outer scope is accessible to the function even after the outer scope has finished execution.
  2. Returning Functions: Closures become especially important when we return a function from another function. This returned function holds on to the variables it had access to when it was declared.
  3. Preserving State: Closures allow functions to “remember” the environment in which they were created. This enables them to preserve state across invocations.
  4. Scope Chain: Each function has access to its own scope, the outer function’s scope, and the global scope (depending on where the function is defined). A closure keeps the variables in the outer scope alive by maintaining a reference to them.

Closures in Action: Practical Examples 🛠️

1. Private Variables

Closures are commonly used to create private variables — variables that can only be accessed by the function that defines them. JavaScript doesn’t have traditional classes with private members, but closures give us a way to simulate this behavior.

function counter() {
let count = 0;
return function() {
count += 1;
return count;
};
}

const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2
console.log(increment()); // 3

In this example:

  • The count variable is private to the counter function and cannot be directly accessed from outside.
  • Each time we call the increment function, the count variable is updated, and the updated value is returned. The state of count is preserved across calls because the increment function closes over the count variable

2. Event Handlers

Closures are frequently used in event handlers in JavaScript. When you attach an event listener to an element, the handler function often needs access to the environment where it was defined.

function attachEventListener() {
let message = "Button clicked!";
document.getElementById("myButton").addEventListener("click", function() {
alert(message);
});
}

attachEventListener();

In this example:

  • The message variable is part of the lexical environment of the event handler function.
  • Even after attachEventListener has finished execution, the event handler retains access to message, so when the button is clicked, it can display the alert.

Common Pitfalls and Best Practices ⚠️

1. Accidental Closure in Loops

One of the most common issues developers face with closures occurs when closures are used inside loops. Let’s look at an example:

for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

You might expect the output to be 1, 2, and 3, but instead, it will log 4 three times. This happens because setTimeout is using the same i variable from the outer scope, and by the time the timeout callbacks are executed, the loop has completed, and i is 4.

Solution: Using let instead of var

The let keyword creates a new binding in each iteration of the loop, so each closure gets its own copy of the i variable.

for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

Now, the output will be 1, 2, and 3, as expected.

When and Why to Use Closures 📌

Closures are an incredibly powerful tool, but knowing when to use them is key. Here are some common use cases for closures:

1. Data Encapsulation

Closures allow you to encapsulate data in such a way that it cannot be accessed from the global scope or modified accidentally. This is especially useful for creating private variables.

2. Callback Functions

When working with asynchronous operations (like event handlers or setTimeout), closures allow you to keep track of variables that might otherwise go out of scope.

3. Function Factories

Closures are often used to create function factories — functions that generate other functions with specific behavior.

function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}

const double = createMultiplier(2);
console.log(double(5)); // 10

Here, createMultiplier returns a function that multiplies any input by a specific value. The returned function closes over the multiplier variable.

Conclusion: Embracing the Power of Closures 🏁

Closures are an essential concept in JavaScript that enables you to create more modular, flexible, and powerful code. They allow functions to preserve their environment, which opens up a range of possibilities — from private variables to function factories to managing asynchronous code.

While closures might seem tricky at first, with practice and a solid understanding of how they work, you’ll start seeing them as a natural and intuitive part of JavaScript. Once you grasp them fully, closures become a tool you’ll reach for again and again in your coding journey.

If you still find closures confusing, don’t worry — it’s normal to need multiple exposures to the concept before it clicks. Keep experimenting, building, and asking questions, and soon closures will be a regular part of your toolkit!

Happy coding! 👨‍💻

--

--

Eftee Codes
Eftee Codes

Written by Eftee Codes

Full Stack Developer | Tech Enthusiast | Psychology & Economics Aficionado | Turning Code into Fun Experiences 🎮🧠

No responses yet