Understanding 'closure'.

Subscribe to my newsletter and never miss my upcoming articles

Developers from various stages of their careers will feel differently about closure. All the way from it's tricky, confusing, to what the heck!?!, it's unnecessary.

But is it though?

Closure is an interesting concept, it's one of those that almost morphs itself and slips into many practical uses, like in event handlers and callback functions, object data privacy and also in functional programming patterns like currying (psst... a post might be coming soon on currying!)

So let's try to understand this, widely perceived as tricky-concept, with some simple workable examples.

But first, quick highlights of some prerequisites:

1. Javascript's lexical Scope is the function scope.

Stuck at huh..? 🤷🏽‍♀️

Well, first off lexical scoping is simply a term for setting the scope of a variable so that it may only be called (referenced) from within the block of code in which it is defined. And in JS, a new lexical scope is created around a new function, i.e. function scope (bear in mind, scope creation happens at call-time) .

Consider this example:

const parentFunc  = () => {
   if(true) {
      let firstVar = 10;
      console.log(secVar); // ReferenceError: secVar not defined
   }

   const childFunc = () => {
      if(true) {
         let secVar = 5;
         console.log(firstVar); // firstVar still known prints 10
      }

      if(true) {
         console.log(secVar); // prints 5
      }
   }
   return childFunc;
}

const myFunc = parentFunc();
myFunc();

In the code above, the variable firstVar is available everywhere inside of parentFunc() and variable secVar is available everywhere within the childFunc(), but neither are available outside of the function where they were defined. This in a nutshell, is lexical scoping for JS.

2. In JS, the search for a variable starts at the innermost scope and goes outwards from there.

If we pay attention in the example above, firstVar isn't defined inside of the childFunc but is still know there. This is because when JS doesn't find a variable in it's local/inner scope, it traverses outwards, in this case to parentFunc, where it finds the definition of firstVar and life's good!

So now, what is Closure?

According to the MDN Almighty, closure is a pattern in which functions can be bundled together along with references to it's surrounding state.

Let's expand on this with a simple example:

const parentAlerter = () => {
  const firstVar = "Counting on closure:";
  let count = 0;
  const childAlerter = () => {
    alert(`${firstVar} ${++count}`);
  };

  return childAlerter;
};

We have two functions here:

  • the outer/wrapper function parentAlerter() which has two variables, firstVar and count, and returns the childAlerter() function. The scopes of both the firstVar and count are limited to the parentAlerter() function.
  • the inner function childAlerter() that alerts with the value of variables firstVar and count within its function body.

Let's add to this example further, and see how closure and lexical scope can work together:

const myAlertOne = parentAlerter(); 
const myAlertTwo = parentAlerter(); 
myAlertOne();//line 3,references the first execution context created
myAlertOne();//line 4,also references the first execution context created
myAlertTwo();//line 5,references the second execution context created
  1. We invoke parentAlerter() twice and store the return values of each of those invocations in two different variables myAlertOne and myAlertTwo . One important thing to note here is that by doing the above, we created two different execution contexts for parentAlerter() , one referenced by the myAlertOne and the other by myAlertTwo. Now with that in mind we move forward.
  2. Because the return value of parentAlerter() is the function body of childAlerter() , myAlertOne and myAlertTwo contain two separate copies of childAlerter() .
  3. When we call myAlertOne() on the third line, we enter the first execution context we created and see an alert with the following: Counting on closure: 1 . Note here that as we would expect after learning about lexical scope, the childAlerter() has access to variables firstVar and count from parentAlerter().
  4. On line 4, when myAlertOne() is invoked again, it references the same execution context as the first myAlertOne() call on line 3 and hence points to the same copies of variables firstVar and count , and we see the output as an alert with: Counting on closure: 2
  5. On line 5, we are now working with the second execution context we created on line 2. This is a brand new copy and has not been affected at all with the workings of the first execution context. And so when we invoke myAlertTwo(), we see an alert with: Counting on closure: 1 .

Create your own (closure) magic:

There are some simple steps to follow with which you can create your own closure functions.

Let's dissect and understand their anatomy:

const parentFunc = () => {  
   let parentVar = "I am";

   const childFunc = () => {
       let childVar = "a pro at Closure now!"; 
       console.log(parentVar+childVar);
    }
   return childFunc;
}

Step 1. First off, define a parent/wrapper function. Here we have parentFunc.

Step 2. Define any number and type of variables that you desire, in the parent function's scope. These will be the variables we wish the child function to have access to. For example, we have the variable parentVar above.

Step 3. Define a child function inside of the parent function's scope. We called it childFunc.

Step 4. And lastly, return that child function at the end of the parent function, so we return childFunc .

And viola! You have your own closure functions, genius.

unnamed.jpg

And now for some... Gotchas!

So hopefully by this point in the article you are feeling like a budding closure ninja ready to conquer the world of JS. But let me make you aware of some last few gotchas.

1. Scope can try to trick you.

Consider the example below:

const superPower = () => {

  const ninja = () => {
    console.log(who);
  };

  let who = 'I am a closure ninja!';

  return ninja;
};
const aceClosure = superPower();
aceClosure();

When we invoke aceClosure on the last line, the console logs I am a closure ninja! . The thing to note here is that even though visibly the variable who is defined after the function ninja , we get no errors and see the desired output.

As mentioned under the prerequisite section earlier, javascript searches for a variable in the inner/self scope and goes outwards from there. In this case, we look for who in the ninja scope where it's not found, so the parent function, in this case superPower 's scope is searched next, and who is found.

Takeaway: Think in terms of function scope instead of just linear parsing.

2. Class aren't the only way to abstraction in Javascript.

Let's dive into this example:

const myTimer = () => {
  let elapsed = 0;

    const myStopwatch = () => { 
        return elapsed; 
    };

    const increment = () => elapsed++;

    setInterval(increment, 1000); // line 10

    return myStopwatch;
};

let timerInstance = myTimer(); // line 15
timerInstance();
  1. When we call myTimer in line 15, an execution context is created and the return value of myTimer, i.e., the function body of myStopwatch is stored in timerInstance. Because we invoked myTimer , the setInterval on line 10 starts to run.
  2. Invoking timerInstance (in the last line) in turn invokes myStopwatch which has to return elapsed.
  3. elapsed is searched for inside of myStopwatch but isn't found, so we start traversing outwards. An instance of elapsed is found under the parent, i.e. myTimer .
  4. From step 1 above, the setInterval has been running all this while, which makes calls to increment and in turn the value stored in elapsed keeps increasing by 1, with every passing second.

Note here that we only have access to timerInstance which invokes myStopwatch , and this function is completely oblivious to how the increment mechanism of elapsed works. In other words, the timerInstance or the myStopwatch functions are abstracted from the increment mechanism.

Here is the output on the console for your reference:

Untitled.png

Takeaway: Getting a good grasp on closure functions can help you implement abstraction without using classes.

And so...

To conclude, I will say what I do in all my posts of this series, javascript concepts can seem strange and sometimes even, unnecessary. But hopefully my writing invokes😉 your curious mind and you see some interesting possibilities with these concepts. Hopefully you'll give closure a chance, and create something fun. Happy coding!

Tapas Adhikary's photo

Good explanation of JS Closure concept, Tanvi! Thanks for sharing.

Tanvi Priyadarshini's photo

Glad you liked it!

Luiz Filipe da Silva's photo

Nice and detailed explanation. Thanks for sharing!