Apa itu closure di javascript?

In this article, we are going to talk about closures in JavaScript. I'll walk you through the definition of a closure, a simple day-to-day fetch utility closure example, and some of the advantages and disadvantages of using closures.

Table of Contents

  • ?

Without further ado, let's get started.

Prerequisites

You should have a good understanding of the following topics to understand this article:

  • How JavaScript's execution context works
  • What the Fetch API is and how to use it

What are closures?

Closures are functions that have access to the variables that are present in their scope chain even if the outer function ceases to exist.

To understand this in more detail, let's understand what a scope chain is. Scope chain refers to the fact that parent scope does not have access to the variables inside its children's scope, but the children's scope does have access to the variables present in its parent scopes.

Let's make this clearer by taking a look at an example below:

let buttonProps = (borderRadius) => {
	const createVariantButtonProps = (variant, color) => {
		const newProps = {
			borderRadius,
			variant,
			color
		};
		return newProps;
	}
	return createVariantButtonProps;
}

As you can see, we have a function called

let primaryButton = buttonProps("1rem"); 
0. This function accepts
let primaryButton = buttonProps("1rem"); 
1 as an argument. Let's consider the
let primaryButton = buttonProps("1rem"); 
0 function as our parent function.

We have another function that has been defined inside the parent function, that is

let primaryButton = buttonProps("1rem"); 
3. This function will accept
let primaryButton = buttonProps("1rem"); 
4 and
let primaryButton = buttonProps("1rem"); 
5 as an argument and return an object that constitutes a variable
let primaryButton = buttonProps("1rem"); 
1 that is present outside its scope.

But a question arises as to how the inner function resolves the variables that are present in the parent scope.

Well, this is possible via lexical scoping. Using lexical scoping, the JS parser knows how to resolve variables present in its current scope or in fact knows how to resolve variables present in the nested functions.

So based on the above explanation,

let primaryButton = buttonProps("1rem"); 
3 will have access to the variables present in its outer function
let primaryButton = buttonProps("1rem"); 
0.

In the above example, the inner function

let primaryButton = buttonProps("1rem"); 
3 is a closure. To understand closures in detail we will first go through the characteristics of closures which are as follows:

  • Even if the outer function ceases to exist, a closure still has access to its parent variables.
  • Closures do not have access to their outer function’s
    const primaryButtonProps = primaryButton("primary", "red");
    0 parameter.

Let's get into more detail on each of these points.

Even if the outer function ceases to exist, it still has access to its parent variables.

This is the basic functionality of any closure. This is their main life motto aka their working principle.

To see this in action we will now execute the above

let primaryButton = buttonProps("1rem"); 
0 function.

let primaryButton = buttonProps("1rem"); 

Calling the

let primaryButton = buttonProps("1rem"); 
0 function will return us another function that is our closure.

Now let's execute this closure:

const primaryButtonProps = primaryButton("primary", "red");

Once the closure is executed, it returns the following object:

{
   "borderRadius":"1rem",
   "variant":"primary",
   "color":"red"
}

Here again a question arises: How does the

const primaryButtonProps = primaryButton("primary", "red");
3 function have access to the variable
let primaryButton = buttonProps("1rem"); 
1 that was not present inside it?

If we go through the definition of closures, and scope chaining that we discussed earlier, it perfectly fits into that instance.

Let's dig deeper into why closures still have access to the variables that are defined outside their scope, even if the outer function ceases to exists – for example,

let primaryButton = buttonProps("1rem"); 
1?  

The answer is simple: closures do not store static values. Instead, they store references to the variables present inside the scope chain. In this way, even if the outer function dies, the inner function, that is a closure, still has access to its parent variables.

Use case of closure: Creating a fetch utility with closures

Now that we've learned what closures are, we will create a nice general purpose utility function. It will handle different request methods such as GET and POST with REST APIs.

For this use case,

  • We will be using JSON placeholder APIs. This provides us with some fake data which we can edit using REST APIs.
  • We will be using JavaScript's fetch API.

Let's first discuss why we even need to design such a utility. There are couple of reasons:

  • For every fetch call, we don’t want to define the base URL (or other common parameters) all the time. So we will create a mechanism that will store the base URL/parameters as a state.
  • To remove redundant code.
  • To provide modularity in the codebase.

Let's dive into the details of this utility. Our fetch utility will look like below:

const fetchUtility = (baseURL, headers) => {
  const createFetchInstance = (route, requestMethod, data) => {
    const tempReq = new Request(`${baseURL}${route}`, {
      method: requestMethod,
      headers,
      data: data || null
    });
    return [fetch, tempReq];
  };

  return createFetchInstance;
};
  • const primaryButtonProps = primaryButton("primary", "red");
    6 accepts two parameters that are
    const primaryButtonProps = primaryButton("primary", "red");
    7 and
    const primaryButtonProps = primaryButton("primary", "red");
    8. These will be used later in the closure to construct the base URL along with the headers.
  • Then we have
    const primaryButtonProps = primaryButton("primary", "red");
    9, which accepts
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    0
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    1 and
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    2 as parameters.
  • Next, this function creates a new request object that will construct our URL using the code:
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    3. We also pass in an object which consists of the request method type, headers, and data if available.
  • Then we return the instance of a fetch API along with the request object.
  • Lastly, we return the
    const primaryButtonProps = primaryButton("primary", "red");
    9 function.

Now let's see this function in action. Call our

const primaryButtonProps = primaryButton("primary", "red");
6 function to initialize the
const primaryButtonProps = primaryButton("primary", "red");
7:

const fetchInstance = fetchUtility("https://jsonplaceholder.typicode.com");
Initializing the baseURL 
  • If we observe, the
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    7 now has the value of the closure of the function
    const primaryButtonProps = primaryButton("primary", "red");
    6.
  • Next, we pass the route and the type of the request to the closure
    {
       "borderRadius":"1rem",
       "variant":"primary",
       "color":"red"
    }
    7:
const [getFunc, getReq] = fetchInstance("/todos/1", "GET");
Executing the closure

As you can see this returns us an array of fetch API instance and the request body that we configured.

Finally, we can make use of the

const fetchUtility = (baseURL, headers) => {
  const createFetchInstance = (route, requestMethod, data) => {
    const tempReq = new Request(`${baseURL}${route}`, {
      method: requestMethod,
      headers,
      data: data || null
    });
    return [fetch, tempReq];
  };

  return createFetchInstance;
};
0 fetch API to call the request
const fetchUtility = (baseURL, headers) => {
  const createFetchInstance = (route, requestMethod, data) => {
    const tempReq = new Request(`${baseURL}${route}`, {
      method: requestMethod,
      headers,
      data: data || null
    });
    return [fetch, tempReq];
  };

  return createFetchInstance;
};
1 like below:

getFunc(getReq)
  .then((resp) => resp.json())
  .then((data) => console.log(data));

We can also create a POST request similar to the above GET request. We just need to call the

{
   "borderRadius":"1rem",
   "variant":"primary",
   "color":"red"
}
7 again as below:

const [postFunc, postReq] = fetchInstance(
  "/posts",
  "POST",
  JSON.stringify({
    title: "foo",
    body: "bar",
    userId: 1
  })
);

And to execute this post request we can do the similar operation that we did for the GET request:

postFunc(postReq)
  .then((resp) => resp.json())
  .then((data) => console.log(data));

If we closely look at the above example, we can see that the inner function

const primaryButtonProps = primaryButton("primary", "red");
9 has access to the variables present in its scope chain. With the help of lexical scoping, during its definition of
const primaryButtonProps = primaryButton("primary", "red");
9 it resolves the variable names.

In this way the closure references the variables

const primaryButtonProps = primaryButton("primary", "red");
7 and
const primaryButtonProps = primaryButton("primary", "red");
8 during its definition even after the outer function
const primaryButtonProps = primaryButton("primary", "red");
6 has ceased to exist.

If we think of closures from a different perspective, then closures help us to maintain a state like

const primaryButtonProps = primaryButton("primary", "red");
7 and
const primaryButtonProps = primaryButton("primary", "red");
8 that we can use across function calls.

Advantages of closures

Here are some advantages of closures:

  • They allow you to attach variables to an execution context.
  • Variables in closures can help you maintain a state that you can use later.
  • They provide data encapsulation.
  • They help remove redundant code.
  • They help maintain modular code.

Disadvantages of closures

There are two main disadvantages of overusing closures:

  • The variables declared inside a closure are not garbage collected.
  • Too many closures can slow down your application. This is actually caused by duplication of code in the memory.

Summary

So in this way closures can be really useful if you want to deal with or implement certain design patterns. They also help you write neat and modular code.

If you liked the idea of closures, then I would recommend reading further on the following topics:

  • Design patterns
  • Anonymous closures

Thank you for reading!

Follow me on Twitter, GitHub, and LinkedIn.

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT


Apa itu closure di javascript?
Keyur Paralkar

Front-end developer👨‍💻; Book enthusiasts📖


If you read this far, tweet to the author to show them you care. Tweet a thanks

Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started