Discover the top 6 benefits of using closures in your JavaScript code, including the creation of private variables and functions and the ability to implement design patterns.
https://www.frontendmag.com/insights/advantages-of-closures-in-javascript/
1. Home Insights Top 6 Great Advantages of Closures in JavaScript
Top 6 Great Advantages of Closures in
JavaScript
AUTHOR
admin
DATE
December 21, 2022
CATEGORY
Insights
Share
Hey there! Are you familiar with the concept of closures? If you’re new to programming or just starting to
learn JavaScript, don’t worry—I’ll explain everything you need to know in this post.
But if you’re already comfortable with closures, you’ll want to stick around for the really cool stuff. We’ll
explore the top 6 advantages of closures in JavaScript, including encapsulation, currying, memoization,
and more.
Closures are incredibly useful for writing efficient JS code, and I’m excited to share all of their potential
with you!
First, let’s take a look at what closures are.
Introduction to Closures
A closure is a function that has access to the variables and parameters of its outer function, even after
the outer function has returned. This is possible because the inner function retains a reference to the
scope of the outer function, even after the outer function has been executed.
Table of Contents
1. Introduction to Closures
2. 1. Encapsulation
3. 2. State Retention
4. 3. Currying
5. 4. Memorization
6. 5. Asynchronous Programming
7. 6. Event handling
8. Advantages of Closures in JavaScript: Conclusion
9. More Resources
Recent posts
Top 20 React Form
Validation Best Practices
December 18, 2022
React Query vs Axios: An
Absolute Beginner’s
Guide to choosing the
Right Library
December 15, 2022
Is TypeScript Faster Than
JavaScript? A
Comprehensive
Comparison
December 13, 2022
How To Master React JS:
The Ultimate Guide (10+
topics)
December 10, 2022
How to Learn React JS
Quickly: Complete
Beginner Guide with 7
Useful Steps
December 8, 2022
HOME TUTORIALS TIPS INSIGHTS CAREER
2. In JavaScript, closures are created automatically when a function is defined within the scope of another
function. Here’s an example:
In this code, we define a function called that declares a variable and a function
called . logs the value of to the console.
When we call , it returns the function. We store the returned function in a
variable called . Then, we call , which prints out the value of .
Because has access to the variables and functions in the scope of , it can log
the value of to the console even though is not directly accessible from outside of
.
In other words, is a function that keeps access to the variables and functions in the scope of
even after has completed execution.
Now that we’ve introduced the concept of closures, let’s move on to the first advantage of using them in
JavaScript.
1. Encapsulation
By encapsulating data and functionality within a closure, you can create self-contained units of code that
are easy to understand and maintain. It can be particularly useful when working on large projects or
when developing libraries or frameworks.
Here is an example of how to use a closure to create a simple book module in JavaScript:
In this example, we have a function that takes a and an as arguments
and returns an object with four methods: , , , and .
These methods allow us to retrieve or update the values of the and variables, which
are private to the function and not accessible outside of it.
By using closures, we are able to create a “book” object that has a clear and defined interface for
interacting with the and properties, while still maintaining the encapsulation of those
properties within the function.
2. State Retention
1. function outerFunc() {
2. const outerVar = "I'm a variable in the outer function";
3.
4. function innerFunc() {
5. console.log(outerVar);
6. }
7.
8. return innerFunc;
9. }
10.
11. const closure = outerFunc();
12. closure(); // "I'm a variable in the outer function"
outerFunc outerVar
innerFunc innerFunc outerVar
outerFunc innerFunc
closure closure outerVar
innerFunc outerFunc
outerVar outerVar
outerFunc
closure
outerFunc outerFunc
1. function createBook(title, author) {
2. let _title = title;
3. let _author = author;
4. return {
5. getTitle: function() {
6. return _title;
7. },
8. getAuthor: function() {
9. return _author;
10. },
11. setTitle: function(newTitle) {
12. _title = newTitle;
13. },
14. setAuthor: function(newAuthor) {
15. _author = newAuthor;
16. }
17. }
18. }
19.
20. const book1 = createBook('Clean Code', 'Robert Cecil Martin');
21. console.log(book1.getTitle()); // 'Clean Code'
22. console.log(book1.getAuthor()); // 'Robert Cecil Martin'
23. book1.setTitle('Code Complete');
24. console.log(book1.getTitle()); // 'Code Complete'
createBook title author
getTitle getAuthor setTitle setAuthor
_title _author
createBook
title author
createBook
3. Consider a function that generates a counter that increments by one each time it is called:
In this example, the function returns a function that increments and returns the value of
the variable each time it is called. Because the returned function is a closure, it keeps access to
the variable even after the function has returned, allowing it to maintain
states across multiple function calls.
This state retention is made possible by the fact that closures keep access to the variables and functions
defined in their parent scope, even after the parent function has returned. It allows you to create function
factories and other patterns that rely on keeping states across several function calls, making closures a
powerful and useful tool for organizing and optimizing your code.
3. Currying
Closures can also be used to create curried functions in JavaScript. Curried functions are functions that
can be called with a partial set of arguments and then return a new function that expects the remaining
arguments. They can be useful for creating more flexible and reusable code by allowing you to specify
some of the arguments upfront and then call the function with the remaining arguments at a later time.
Here is an example of a curried function in JavaScript:
In this example, we have a function that takes a single argument and
returns a new function that takes a single argument . This returned function adds the to
the beginning of the and returns the result.
We use the function to create different formatter functions. Each of these functions
expects a single argument and can be used to add a prefix to a value in a clean and efficient way.
In this code, we create the function to add a dollar sign to a price, and the
function to add a percentage sign to a percentage value. These functions can then
be used in various contexts, such as in string interpolation or as part of a larger expression.
Overall, closures allow you to specify a common prefix that can be used in multiple contexts without
having to repeat it each time.
4. Memorization
Memorization is a technique that involves storing the results of expensive or time-consuming calculations
in a cache or lookup table so that they can be quickly retrieved the next time the same calculation is
needed. It can greatly improve the performance of a function or algorithm, especially if it is called multiple
times with the same arguments.
1. function createCounter() {
2. let count = 0;
3. return function() {
4. count += 1;
5. return count;
6. }
7. }
8.
9. const counter1 = createCounter();
10. const counter2 = createCounter();
11.
12. console.log(counter1()); // 1
13. console.log(counter1()); // 2
14. console.log(counter2()); // 1
createCounter
count
count createCounter
1. function createFormatter(prefix) {
2. return function(value) {
3. return prefix + value;
4. }
5. }
6.
7. const formatCurrency = createFormatter('$');
8. const formatPercentage = createFormatter('%');
9.
10. console.log(formatCurrency(123.45)); // $123.45
11. console.log(formatPercentage(0.1234)); // %0.1234
12.
13. const price = 123.45;
14. console.log(`The price is ${formatCurrency(price)}`); // The price is $123.45
15.
16. const percentage = 0.1234;
17. console.log(`The percentage is ${formatPercentage(percentage)}`); // The
percentage is %0.1234
createFormatter prefix
value prefix
value
createFormatter
formatCurrency
formatPercentage
4. Here is an example of how you might use closures to improve performance through memorization in
JavaScript:
In this example, we have a function that creates and returns a function
for generating Fibonacci numbers. The generated function has a private object that stores
previously calculated Fibonacci numbers.
When the generated function is called with a number , it first checks to see if the result is already
stored in the . If it is, it simply returns the cached result, which is much faster than recalculating it.
If the result is not in the cache, the function calculates the Fibonacci number and stores it in the cache
before returning it.
By using a closure, we are able to create a “memoized” version of the Fibonacci function that greatly
improves its performance through the use of memorization. This is especially useful for expensive or time-
consuming calculations that may be called multiple times with the same arguments.
5. Asynchronous Programming
Here is a code example that demonstrates how closures can simplify asynchronous programming in
JavaScript:
In this example, we have a function that returns a new Promise that wraps an HTTP request
to a given URL. The Promise is resolved with the response data from the server when the request is
successful, or rejected with the HTTP status code when the request fails.
The event handler of the object is defined within the
Promise’s executor function, which is a closure. This means that the event handler has access to the
variables and functions defined in its parent scope, including the object, the and
functions, and the argument.
By using closures in this way, we can simplify the asynchronous programming process by encapsulating
the logic for handling the response data within the Promise’s executor function, rather than having to
define separate event handlers for each possible response. This makes our code easier to read and
maintain, as we can define the logic for handling the response data right where it’s needed.
6. Event handling
1. function createFibonacciGenerator() {
2. const cache = {};
3.
4. return function fibonacci(n) {
5. if (n in cache) {
6. return cache[n];
7. } else {
8. let a = 0, b = 1, c;
9. for (let i = 0; i < n; i++) {
10. c = a + b;
11. a = b;
12. b = c;
13. }
14. cache[n] = a;
15. return a;
16. }
17. }
18. }
19.
20. const fibonacciGenerator = createFibonacciGenerator();
21. console.log(fibonacciGenerator(10)); // 55
22. console.log(fibonacciGenerator(10)); // 55
createFibonacciGenerator
cache
n
cache
1. function getData(url) {
2. return new Promise((resolve, reject) => {
3. const xhr = new XMLHttpRequest();
4. xhr.onreadystatechange = function() {
5. if (xhr.readyState === 4 && xhr.status === 200) {
6. resolve(JSON.parse(xhr.responseText));
7. } else if (xhr.readyState === 4) {
8. reject(xhr.status);
9. }
10. }
11. xhr.open('GET', url);
12. xhr.send();
13. });
14. }
15.
16. getData('https://your-domain.com/api/users')
17. .then(users => console.log(users))
18. .catch(error => console.error(error));
getData
onreadystatechange XMLHttpRequest
xhr resolve
reject url
5. Closures can also be useful for creating event handlers in JavaScript. Here is a code example that
demonstrates the use of closures for event handling:
In this example, we have a function that takes an array of menu items as an argument and
returns an object with three methods: , , and . The and
methods are used to navigate between the different menu items, while the method is
an event handler that is called in response to a user pressing a key on the keyboard.
The event handler checks the key code of the pressed key and either calls the
or method to navigate to the previous or next menu item, depending on the key that was pressed.
Because the returned object is a closure, it retains access to the and variables
even after the function has returned. This allows us to create multiple instances of the
menu object, each with its own separate list of items and current item, without having to worry about
conflicts or interference between the different instances.
The method can then be used as an event handler by adding it as a listener to the
event on the object using the method. We use the
method to specify that the value inside the event handler should refer to the menu object so that
we can access the and methods.
This code example demonstrates how closures can be used to create event handlers in JavaScript,
allowing you to retain access to variables and functions defined in the parent scope and specify complex
event-driven behavior in a clean and efficient way.
Advantages of Closures in JavaScript: Conclusion
I hope you enjoyed learning about the top 6 advantages of closures in JavaScript. Closures are such a
useful feature of the language that can make your code more powerful, efficient, and flexible.
In case you missed any of the 6 benefits we covered in this post, here they are one more time:
1. Encapsulation: Closures allow you to create private variables and functions that can only be accessed
from within the closure. This can help you improve the structure and modularity of your code.
2. State retention: Closures allow you to retain the state of a function even after it has finished running.
This can be handy for creating function factories or other patterns that rely on maintaining state
across multiple function calls.
3. Currying: Closures can help you create curried functions, which are functions that can be called with a
partial set of arguments and then return a new function that expects the remaining arguments. This
can make your code more flexible and reusable, which is always a good thing!
4. Memorization: Closures can be used to implement the memorization technique, which allows a function
to remember and reuse its previous results. This can help you improve the performance of your code.
5. Asynchronous programming: Closures can simplify asynchronous programming by allowing you to
define and execute callback functions or async logic within the context of the asynchronous operation,
rather than having to define them separately and pass them around as arguments.
1. function createMenu(items) {
2. let currentItem = 0;
3.
4. return {
5. next: function() {
6. currentItem = (currentItem + 1) % items.length;
7. return items[currentItem];
8. },
9. prev: function() {
10. currentItem = (currentItem - 1 + items.length) % items.length;
11. return items[currentItem];
12. },
13. handleKeydown: function(event) {
14. if (event.keyCode === 37) {
15. // left arrow key
16. console.log(this.prev());
17. } else if (event.keyCode === 39) {
18. // right arrow key
19. console.log(this.next());
20. }
21. }
22. }
23. }
24.
25. const menu = createMenu(['Home', 'About', 'Contact']);
26.
27. document.addEventListener('keydown', menu.handleKeydown.bind(menu));
createMenu
next prev handleKeydown next prev
handleKeydown
handleKeydown prev
next
currentItem items
createMenu
handleKeydown
keydown document addEventListener bind
this
prev next