Intricacies of Closures in Javascript

Prerana NawarPrerana Nawar
4 min read

Hey peeps!

Today, we're diving into a fascinating and somewhat magical aspect of JavaScript - closures.

So buckle up, and let's unravel the mysteries together.

The Essence of Closures

At the heart of JavaScript lies the concept of closures. Imagine a function bundled along with its lexical scope, creating a magical bond. This phenomenon is what we call a closure. To make it more concrete, let's take a look at a simple example:

function createClosure() {
  let outerValue = 'I am from the outer function';

  function innerFunction() {
    console.log(outerValue);
  }

  return innerFunction;
}

createClosure();
// Output: I am from the outer function

In the innerFunction, a closure is formed with the variable outerValue, which is part of the lexical scope of createClosure. This implies that innerFunction has access to its parent's lexical scope, creating a captivating connection.

Functions: The Heart of JavaScript

Functions are the backbone of JavaScript, and they can be expressed in various ways. The fact that functions can be used as Javascript with various forms, sometimes makes closure a complex topic to understand.

For example, returning a function from a function.

Return a Function

function x() {
  var a = 7;
  function y() {
    console.log(a);
  }
  return y;
}
x();

The intriguing part here is figuring out what this returns and what the output might be. Can you guess?

The Complexity of Closures

Closures can add complexity to your code, but they also bring powerful capabilities. Consider the following scenarios:

Closures in Action

function x() {
  var a = 7;
  return function y() {
    console.log(a);
  };
}
var z = x();
console.log(z);
z();

In this snippet, not only is the function returned, but the entire closure (function y along with its lexical scope) is stored in z. So, even when z is used elsewhere in the program, it remembers the variable a inside x().

The Reference Persists

function referencePersists() {
  let outerValue = 7;

  function innerFunction() {
    console.log(outerValue);
  }

  outerValue = 100;

  return innerFunction;
}

const persistedReferenceFunction = referencePersists();
console.log(persistedReferenceFunction); // Output: function () { console.log(outerValue); }
persistedReferenceFunction(); // Output: 100

Here, outerValue doesn't refer to the value 7; it refers to a's reference. The value 7 doesn't persist, but the reference to outerValue does.

A Deeper Example 1:

function createCounter() {
  let count = 0; 

  function increment() {
    count++;
    console.log("Count incremented:", count);
  }

  function decrement() {
    count--;
    console.log("Count decremented:", count);
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    decrement,
    getCount,
  };
}

const counter1 = createCounter();
const counter2 = createCounter();

counter1.increment(); // Output: Count incremented: 1
counter1.increment(); // Output: Count incremented: 2
counter2.decrement(); // Output: Count decremented: -1

console.log(counter1.getCount()); // Output: 2
console.log(counter2.getCount()); // Output: -1

Closures enable each counter object to have its own encapsulated state, preventing external access to the count variable. This is a prime example of how well closures work when defining private variables and functions inside factory functions.

A Deeper Example 2:

function deeperExample() {
  let outerValue1 = 900;

  return function () {
    let outerValue2 = 7;

    function innerFunction() {
      console.log(outerValue2, outerValue1);
    }

    innerFunction();
  };
}

const deepClosure = deeperExample();
deepClosure(); // Output: 7 900

The function innerFunction forms a closure with the scopes of both function and deeperExample, creating a chain of closures.

A Deeper Example 3:

function createMultiplier(base) {
  return function (multiplier) {
    return function (value) {
      console.log(`Result: ${base * multiplier * value}`);
    };
  };
}

const doubleMultiplier = createMultiplier(2);
const tripleMultiplier = createMultiplier(3);

const doubleAndTriple = tripleMultiplier(2);
doubleAndTriple(5); // Output: Result: 30

In this example, closures enable the innermost function to access not only its local variables (base, multiplier, value) but also the variables from its parent functions (base and multiplier). This creates a chain of closures, allowing for a powerful and flexible function factory.

Summing It Up

In a nutshell, a closure is a function that retains access to its outer function's scope even after the function has returned. It's like a piece of magic that keeps the connection alive.

Thank youu :)

0
Subscribe to my newsletter

Read articles from Prerana Nawar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Prerana Nawar
Prerana Nawar