Understanding Closures in JavaScript

·

6 min read

Understanding Closures in JavaScript

Introduction

JavaScript closure is a concept that is often difficult to understand. The purpose of this article is to educate you about JavaScript closures and how to utilize them in a real-world project.

Before delving into the details of JavaScript closures, it's necessary to grasp two key terms;

  • scope
  • lexical scoping.

Scope

The scope of your code specifies the region in which variables, functions, and objects are available during execution. This means that the placement of a variable declaration determines its scope (where it may be accessed). Variable accessibility is determined by where those variables are placed in your code thus depending on where your variables are written, some part of your program may not have access to them. When a function is created, it creates its execution context with its own scope

function person() {
  const name = 'omah';
  console.log(name)
}
person();  //omah
console.log(name);  //ReferenceError: name is not defined

Outside the scope of the person() function, the name variable is not accessible. As a result, JavaScript throws a reference error.

Lexical scoping

In lexically scoping, the function uses variables in the context in which it was declared, rather than in the scope where it is invoked.

let greeting =  'Good morning!';
 console.log(greeting); // -> 1

function innerGreeting() {
   console.log(greeting); 
}

function outerGreeting() {
   let greeting = 'Good night!';
   console.log(greeting); // -> 2

   innerGreeting(); // -> 3
}

outerGreeting(); 
1. Good morning!
2. Good night!
3. Good morning!
  1. innerGreeting() has access to the value of the greeting variable from its lexical scope.
  2. When outerGreeting() is invoked, innerGreeting() obtains and recalls the greeting value from its lexical scope after being called outside of where it was defined.

How are closures important?

Using three different approaches, I will create a counter function that will increment a number each time the function is called.

Method 1

let num = 0;
function counter(){
     num++
    return num;
}
counter()//1
counter()//2

The above method of creating a counter function by declaring the num variable in the global state to keep track of the current state of the variable works as expected.

Nevertheless, this approach comes with a drawback. The num variable declared in the global state can be mutated by another function running in our program thereby altering the result of num when the counter function is being called.

Method 2

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

The method of defining the increment num variable within the counter() function will not work since it will always return 1 whenever the function is called.

When a pure function is removed from the call stack, the memory that holds the function's variables is cleaned. As a result, the value of num is set to its initial state.

Finally, we'll want to use a method that increments the num without defining the variable in the global state. This may be accomplished with the help of a CLOSURE.

What is a Closure

A closure is the combination of a function and the environment in which the function is declared. The environment consists of the local variables that were in scope at the time that the closures were created. Closures give the inner function access to an outer function scope, remember the variables of its outer function (lexical scope), and can access them regardless of where it is invoked.

When a function refers to data outside of its scope (an outer function or global state), the JavaScript interpreter creates a closure and stores the variables in heap memory, this stores variable for a longer period as opposed to the stack memory.

All functions in JavaScript are closures, although the majority of them are called within the scope in which they were declared. Closures become significant when they are invoked outside the scope they were defined. There are several practical reasons why using closures is important one is for data encapsulation. To create a closure,

  • Define an inner function in an outer function
  • Return the inner function. The data contained in the inner function is kept private and won't be exposed to the surrounding environment.

closure-info-01.jpg

Method 3

 function makeCounter() {
   let count = 0;
   function increment() {
     count++;
     console.log(count);
   }
   return increment;
}
let increase = makeCounter() 
increase() // 1
increase() // 2

Let's take a deeper look at the implementation to make the most of it:

  • makeCounter which is the outer function that defines a namespace. When it is initially invoked, It sets a count variable to 0 and returns an inner function called increment.

  • The logic for incrementing the count variable is defined in the increment(inner) function.

  • This closure function (increment) preserves the value of count(the private variable)

  • After makeCounter function is executed again, the function still has access to the count variable of its parent scope that has been destroyed. Therefore, the increment function adds 1 to the count variable.

Practical use of JavaScript Closure

Let's create a program that changes the color of the text in the HTML element based on the button clicked

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <ul>
      <li>I love javaScript</li>
      <li>Python is a lovely language</li>
      <li>Try Go</li>
    </ul>
    <button id='blue' style="color:blue">blue</button>
    <button id = 'green' style="color:green">green</button>
    <button id='maroon' style="color:maroon">maroon</button>

  </body>

</html>
// without  Js closures - Method 1
  //  change the text color to green
    document.getElementById('blue').onclick =  function() {
       document.body.style.color = 'blue'
  }
//  change the text color to green
    document.getElementById('green').onclick =  function() {
       document.body.style.color = 'green'
  }
  //  change the text color to green
    document.getElementById('maroon').onclick =  function() {
       document.body.style.color = 'maroon'
  }
  }

The above method will lead to a repetition of same the function but with Js closures, We can write a single function to solve this.

function changeTextColor(color) {
  return function() {
    document.body.style.color = color
  }
}
document.getElementById('blue').onclick = changeTextColor('blue');
document.getElementById('green').onclick = changeTextColor('green');
document.getElementById('maroon').onclick = changeTextColor('maroon');

Screen Recording 2021-11-06 at 02.14.33.gif

  • The anonymous function (which has its scope) defined in the changeTextColor function captures a reference to the color parameter defined in its outer scope.
  • When changeTextColor function is invoked and the value of the color is passed to it, a function(closure) that captures that color is returned.

In summary

A closure has three scope chains:

  • It has access to its scope, which is specified by variables defined between its curly brackets.
  • It has access to the variables of the outer function.
  • It is possible to access global variables.

Thank you for taking the time to read this. If you found this post useful, please share it and give it some thumbs up. I'd love to read your comments as well.