How Closure Works In JavaScript

Closure is an essential concept not just in JavaScript, but in programming, that every developer should understand...

Introduction

What exactly is closure, and why should you care about knowing how it works? Closure is an essential concept in JavaScript that every developer should understand. In this article, you will learn how closure work, and explore the numerous benefits it brings. So grab your favorite beverage, buckle up, and get ready to embark on an exciting journey into JavaScript closure!

Prerequisites

Before diving into closure:

  • You should know how to create and call a function in JavaScript.

  • You should have a good understanding of how scope and the scope chain work.

What exactly is closure?

When you write a function inside another function in JavaScript, the inner function has a special ability called “closure”. This means that the inner function can "see" and use all the variables that were defined in the outer function, even after the outer function has finished running. So, if you have a variable that's defined in the outer function, you can use that variable in the inner function.

If that seems overwhelming, here's another way to think about closure. You work at a company with different departments, each department has its own set of tools and resources, but some resources are shared between departments. When you need to use a resource from another department, you can access it because you have a key or a pass that allows you to enter that department's space.

In JavaScript, functions act like departments, and variables and functions declared inside a function are like the resources that belong to that department. When an inner function needs to access a variable or function from an outer function, it can do so because it has a "key" or reference to the outer function's scope, even if the outer function has already finished executing.

To better understand closures, let's take a look at a simple example:


const numberOfClicks = function () {
  let clicks = 0;

  return function () {
    clicks++;
    console.log(clicks);
  };
};
const clicked = numberOfClicks(); 
clicked(); // clicks is now 1
clicked(); // clicks is now 2
clicked(); // clicks is now 3
clicked(); // clicks is now 4

In the above example, numberOfClicks is a function that returns another function. The inner function keeps track of a variable called clicks which is defined in the outer function.

Each time the inner function is called, it increases the value of clicks by one. This is made possible by the closure created, which keeps the clicks variable accessible to the inner function even after numberOfClicks has finished executing.

Note: An inner function can access variables declared in its parent scope, but the outer function cannot access variables within the inner function(s).

How does closure work?

When you define a function in JavaScript, it creates a scope that includes all of the variables and functions defined within it. This scope is sometimes called the "local scope" of the function. When the function is invoked and executed, JavaScript creates a new execution context (an environment where a piece of JavaScript code is executed. This is an abstract concept) for it, which includes its local scope. Once the function finishes executing, its local scope is destroyed, and any variables or functions defined within it are no longer accessible.

Nevertheless, if a function returns another function or if its inner function uses a variable from the outer function, the inner function maintains a reference to its original local scope. This reference is known as closure. This means that any variable or function defined in the parent function's local scope is still accessible to the inner function, even after the original function has finished executing and its local scope has been destroyed.

To put it simply, closures are possible in JavaScript because the inner function maintains a reference to the outer function's execution context because it is within the scope chain of the outer function, even after the outer function has finished executing. This reference allows the inner function to access the variables and functions defined in the outer function, creating a closure.

Scope chain of Closures

Let's take a moment to appreciate that every closure has not one, not two, but THREE scope chains!

  • It has access to the global scope - variables that are not within any function or curly brackets. These variables can be accessed anywhere in your code.

  • Local scope - It refers to variables and functions that are defined within a function and can only be accessed within that function or any nested functions inside it.

  • Enclosing scope (can be block, function, or module scope).

Closure in practice

Encapsulating data and behavior (methods):

Closure can be used to encapsulate data and behavior (methods) within a function. This can be useful for creating reusable code that hides implementation details from the user.

function counter() {
  let count = 0;
  return function() {
    count++;
    console.log(count);
  }

}

const counter1 = counter();
counter1(); // logs 1
counter1(); // logs 2
counter1(); // logs 3

const counter2 = counter();
counter2(); // logs 1
counter2(); // logs 2

The counter function returns a function that increments and logs a count variable each time it is invoked. The count variable is encapsulated and can only be accessed or modified by the calling function. This allows us to create multiple independent counters with private count variable.

Callback functions and event handling:

Closure can be used to create callback functions that retain access to the surrounding state (data) when they are called.

function waitThenCall(callback) {
  const delay = Math.floor(Math.random() * 1000);
  setTimeout(() => {
    callback(delay);
  }, delay);
}

function logDelay(delay) {
  console.log(`Delayed for ${delay} milliseconds`);
}

waitThenCall(logDelay); // logs "Delayed for <random delay> milliseconds"

The waitThenCall function takes a callback function and delays calling it by a random amount of time. The logDelay function is used as the callback, and it logs the delay when it is called. Because the logDelay function is defined in the same scope as the waitThenCall function, it retains access to the delay variable even after the waitThenCall function has finished executing.

Asynchronous operations and promises:

Closure can be used to create asynchronous operations and promise that retain access to the surrounding state (data) when they are resolved.

Performance concerns

When we create a function in JavaScript, each instance of that function has its scope and closure. This means that if we create functions within other functions that do not require closure, it can slow down the performance of our script and use up more memory than necessary. It is important to only use closure when we need them to maintain encapsulation or other functionality, rather than creating unnecessary layers of nested functions. This will help our code to run more efficiently and improve the overall performance of our scripts.

conclusion

By now, you must have gotten a good understanding of how closure works in Javascript. As a final note, it is important to use closure judiciously, as creating unnecessary layers of nested functions can negatively affect script performance in terms of processing speed and memory consumption. By understanding how closure work and when to use them, we can write more efficient and effective JavaScript code.

That's all folks! Catch you on the flip side!

Credits