Bytes
Web Development

What is Closure in JavaScript? Explained with Easy Examples

Published: 11th April, 2023
icon

Arunav Goswami

Web Development Consultant at almaBetter

Have you ever faced a scenario where you needed to design a function containing specific variables that must remain inaccessible from outside the function? Picture yourself creating an e-commerce website that lets customers add products

Frame 854.png

Have you ever faced a scenario where you needed to design a function containing specific variables that must remain inaccessible from outside the function? Picture yourself creating an e-commerce website that lets customers add products to their cart. The website should feature several buttons on the page, enabling customers to add various items to their cart. You may have been puzzled about how to manage multiple button clicks. This is where the magic of closures comes into play, resolving such issues. By employing closures, you can establish a private context for each button click listener, guaranteeing that each listener manages its distinct set of variables.

Closures represent a potent concept in JavaScript, enabling functions to access variables from their parent function even after the parent function has finished executing. Does that sound complicated? Let's simplify it.

What are JavaScript Closures?

Frame 856.png

A closure is simply a function that has access to variables in its parent scope, even after the parent function has finished executing. This is made possible because when a function is created, it forms a closure with the variables in its surrounding environment.

In other words, a closure allows a function to "remember" the values of variables that were in scope at the time it was created. This means that even if the variables are no longer in scope when the function is executed, it can still access them.

Scopes and Closures

Frame 857.png

As a JavaScript developer, you've likely encountered the term "lexical scope." But what does it signify, and how is it connected to closures?

Fundamentally, lexical scoping governs the resolution of variables in nested functions or code blocks. It establishes an accessibility hierarchy, allowing functions, variables, and objects to be accessed based on their position in the source code.

To gain a clearer understanding of this concept, let's examine an example:

let var1='global';

function outer(){
  let var2='outer';

  function inner(){
    let var3='inner';

    console.log(var3);  //prints 'inner'
    console.log(var2);  //prints 'outer'
    console.log(var1);  //prints 'global'
 }

  console.log(var1);    //prints 'global'
  console.log(var2);    //prints 'outer'
  inner();
}

outer();
console.log(var1); //prints 'global'

In this example, we have three different levels of lexical scope: the global scope, the outer function's scope, and the inner function's scope. The inner function can access variables defined in its own scope, the outer function's scope, and the global scope. This is where closures come in.

The inner function is referred to as a closure, as it can access the lexical scopes of both the outer and global functions. By establishing a private context for each function, closures enable the preservation of a function's state even after it has been returned or invoked multiple times.

Some Examples

Frame 858.png

To understand in depth how closures work, let's take a look at some examples:

Example1

function person() {
  let name = 'Peter';
  
  return function displayName() {
    console.log(name);
  };
}
let peter = person();
peter(); // prints 'Peter'

In this code, the person function is called, which returns the inner function displayName, which is then saved in the Peter variable. The name "Peter" is printed to the console when we use the Peter function, which is actually accessing the displayName function.

But, the displayName function doesn't have a variable with the name. Therefore, even after the outer function person has returned, this function can still access the variable person. Hence, the displayName function is a closure.

Example2

function multiplyBy(num) {
  return function(x) {
    return x * num;
  }
}

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

In this example, the **multiply** function returns a closure that multiplies a given number by a constant **num**. This closure can be stored in a variable and then called later with a different value to create a callback that multiplies any number by the same constant **num**.

These are just a few examples of how closures can be used in JavaScript to create private variables, encapsulate code, and create callbacks. Closures are a powerful and versatile feature of JavaScript and can be used in many different ways to create modular and reusable code.

Why Use Closures?

So why do we need closures in JavaScript? The reason can be traced back to JavaScript being a "function-scoped" language. In other words, variables declared within a function are only accessible within the scope of that specific function. This makes it challenging to establish "private" variables that remain inaccessible from outside the function. Closures enable the creation of private variables and functions by enclosing them within the scope of another function.

An additional advantage of employing closures is in the context of Memoization. Closures can be utilized for memoization, a method for optimizing resource-intensive function calls by caching the outcomes of earlier calls. By leveraging closures to retain cached values, it is possible to safeguard the cached data from being accessed or altered by other segments of the code.

Some Real World Use Cases

Frame 859.png

For those who have developed websites, it's well-known that event listeners are the cornerstone of web development. They enable us to craft engaging, interactive experiences for users. But what about preserving the state of an event listener even after it has been activated? This is where closures step in!

Consider the scenario where you wish to construct a button that increments a counter with each click. By employing closures, you can generate a private scope that upholds the counter's value even after the event listener has been activated. Consequently, this allows you to monitor the number of times the button has been clicked and execute different actions depending on the count.

Here's an example code snippet that demonstrates how closures can be used with event listeners:

const button = document.querySelector('#my-button');
let count = 0;

button.addEventListener('click', () => {
  count++;
  console.log(`Button clicked ${count} times!`);
});

In this example, we're using a closure to maintain the value of the **count** variable across multiple function calls. Each time the button is clicked, the event listener increments the **count** variable and logs a message to the console. Because the **count** variable is declared outside the event listener function, it's accessible from within the function, and its value persists across multiple function calls.

Closures can also be valuable in practical scenarios, such as developing desktop or mobile applications that require managing user input. For example, consider the construction of a text editor application that enables users to generate and modify documents. To incorporate features like undo and redo, closures can be employed to establish a private context for every document instance. This way, each instance can maintain its unique set of variables, like the content of the document and its edit history, which can be accessed and manipulated by undo and redo operations.

Conclusion

JavaScript closures are a powerful concept that can greatly improve the functionality and security of your code. They allow you to create private variables, hide sensitive data, and optimize function calls, among many other applications.

By understanding how closures work and how to use them effectively, you can unlock the full potential of functions in JavaScript. So next time you find yourself in a situation where you need to create a function with private variables, remember the power of closures and let them work their magic!

Interview Questions

  1. Explain how closures can be used for memoization?

Answer: Memoization is a strategy for optimizing performance by caching the outcomes of resource-intensive function calls and returning the cached outcomes when identical inputs are encountered in later calls. By employing closures to store cached values in the scope of a parent function, memoization can be achieved, which safeguards the cached information from being accessed or altered by other code segments.

  1. What are the possible drawbacks of using closures, and how can they be addressed?

Answer: One possible drawback of utilizing closures is that they can contribute to heightened memory usage since they maintain references to their parent function's scope even after the parent function has finished executing. In some instances, this may result in memory leaks if not handled appropriately. To address this issue, you can employ weak references (for example, using **WeakMap** or **WeakSet** in JavaScript) or attentively manage the lifespan of closures and their references, ensuring that unneeded data is not retained in memory.

  1. How can you avoid memory leaks when using closures in JavaScript?

Answer: Memory leaks can occur when using closures in JavaScript, especially if closures are created within loops or other frequently executed code. Here are some strategies for avoiding memory leaks when using closures:

  1. Avoid creating unnecessary closures: Closures should only be created when they're needed and should be kept as simple as possible to avoid consuming unnecessary memory.
  2. Minimize the scope chain: The scope chain can become long and complex when using closures, which can make it difficult to manage memory effectively. To avoid this, try to keep the scope chain as short as possible by creating closures within functions that have a limited scope.
  3. Use closures with care in event listeners: Event listeners can be a common source of memory leaks if closures are created within them. To avoid this, make sure to remove event listeners when they're no longer needed, and avoid creating closures within event listeners if possible.

Related Articles

Top Tutorials

AlmaBetter
Made with heartin Bengaluru, India
  • Official Address
  • 4th floor, 133/2, Janardhan Towers, Residency Road, Bengaluru, Karnataka, 560025
  • Communication Address
  • 4th floor, 315 Work Avenue, Siddhivinayak Tower, 152, 1st Cross Rd., 1st Block, Koramangala, Bengaluru, Karnataka, 560034
  • Follow Us
  • facebookinstagramlinkedintwitteryoutubetelegram

© 2024 AlmaBetter