Get closer to Closures🚢in JavaScript ❀️ - Part 1

Get closer to Closures🚢in JavaScript ❀️ - Part 1

This article Part 1 will make you familiar with the notorious topic "How Closures work in JavaScript".

Β·

7 min read

Closures definition 🀯

Just like we refer to the definition of any topic, let's take a moment to read the definition of Closures mentioned in the MDN.

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

This definition is just going over your head, right? No worries, I also slipped through this moment and I will try my best to walk you through all of it.

Just a "normal" function πŸ˜‹

We noticed that the definition revolved around the "function" keyword most of the time, right? So why not just start with a basic simple code snippet of a function?

function sum (x, y){
  return x+y
}

console.log(sum(10, 20));

// Output: 30

This was pretty simple, a function named "sum" takes two parameters "x" and "y" and returns the sum "30" when we pass the arguments "10" and "20" and there wasn't much to deal with in this.

An outer function and an inner function πŸ‘€

What if I tell you that we will have a function that does the same thing of returning the sum as above but this time, it would look somewhat different?

function sum (x) {
  function inner (y) {
    return x + y 
  }
  return inner
}

Let's discuss first how we created it.

  • There is a function named "sum" which can be also called an outer function and has the parameter "x".

  • Inside this "sum" function, we see the "inner" function sitting which also has the parameter "y" and returns the total "x" + "y".

What does the "sum" function return? It returns the inner function. We'll get more into it.

How to call it πŸ“ž?

Well, this is not calling. πŸ˜‚

It's simple to understand how we invoke the function, let's take a look.

  • Remember? we had pointed out that function "sum" returns the whole inner function. That's what we see in the output which means we get an inner function and it needs to be called too right? Let's do it.

See, we get 30 the expected output. Well, now you have understood, you can also see it being called concisely too in the below code snippet.

console.log(sum(10)(20)); 
// It would also work by directly calling, the only thing is just that 
// there is no variable "store", storing the function calling.

But why not that simple function πŸ™„?

By now, I should tell you what is the reason why we earlier created a simple function and later a different kind, which also returns the same output. Because we will uncover so-called fancy terms that revolve around the definition of closures. Are you ready to see the spilled beans?

  • First thing, we know that the parameters passed in a function are its local variables only. So, let's modify the previous code and make it look something like this.

      function sum () {
        var x = 20
        function inner () {
          var y = 10
          return x + y 
        }
        return inner
      }
    
      let store = sum();
      console.log(store());
    
      //Output
      30
    
  • Let's form an execution context model in our mind of this code.

  • Global execution context: In its memory creation phase, the sum function is stored and the variable store is assigned a special placeholder value undefined.

  • Next, in its Code component/execution phase, it comes to the line let store = sum(); and when the sum() is called, the execution context for the sum function is created and the memory creation phase begins.

  • In the execution phase of the sum function context, the variable x is assigned the value 20.

  • Later the JS engine comes to the line return inner and the inner function is returned to the variable store that will no longer carry an undefined placeholder now. Since there will be nothing left to execute in the function sum() execution context, it will just pop out of the call stack.

  • The JavaScript engine has now the line console.log(store()) to execute. As soon as, it comes to this line, the inner function is invoked and the inner() function execution context is created.

  • The variable y will also get assigned the value 10 now as the code execution phase gets started.

  • I know, so far what we discussed is familiar to you but do you also happen to wonder, what happens when this JS engine goes to the inner() function's return statement which is return x + y.

  • Because now, we no longer hold that sum() function execution context, how will we bring 10, the reference of x in the return statement? Interesting and exciting? We're getting close to this.

  • The answer is closure. The inner function forms a closure with function sum. Think of it like this, the sum() function says: "I may not be present in the call stack but the JS engine will keep the reference of my variable "x" for the inner() function which has formed a closure relationship with me.

  • Thus, the inner function returns the value "x" + "y" = 30 and its execution context also gets popped out followed by the global execution context.

  • This is how it works.

Also note: there is a term garbage collection, it is done automatically in the background where it retains the memory of objects until they become unreachable and then it releases them when they are no longer in use.

Had it not been for the closure, then the variable"x" would have been garbage collected. Suppose the sum() function had a variable named "a" which was not required to use in the inner() function, then only "x" would be present in the closure and "a" would be garbage collected, this means "a" has been smartly garbage collected.

Those fancy terms πŸ€”?

Of course, it will be easier to understand now the lexical scope and the lexical environment.

function sum () {
  var x = 20
  function inner () {
    var y = 10
    return x + y 
  }
  return inner
}

Lexical Scope πŸ€·β€β™€οΈ

  • It simply means allowing the function to access its parent's scope.

  • Here, the lexical scope of the inner() function is its parent sum() function's scope, where it is sitting inside and it can access everything present inside the sum() function.

  • That was simple. Then, what is a lexical environment? Of course, let's see that too.

Lexical Environment πŸ‘ͺ

  • The lexical environment has two things: the function's local memory and the reference to its lexical scope. Take a look below:

  • Observe that since the beginning, I have been emphasizing the term "reference" not the term "value" because the inner function will always remember the reference of "x" present in its lexical environment or the memory address of "x".

  • Before you get confused, let's modify the code, we will assign the value 30 to "x" before the return statement inside the inner() function and see if it proves by holding the reference not just its value.

  • I know it must be effortlessly easy for you to understand now. You can also revisit the definition of "closure" MDN definition too. It will all start making sense now.

Conclusion 🌟

We started with a normal function and ended up understanding the closure definition or we can say a beautiful concept of closures. But Wait, it was just easy but not beautiful yet. I know your reaction to what did I just say.

It is because, in Part 2, we will discover the beauty of Closures with practical examples in depth and how advantageous this topic is. We will fall in love ❀️ with Closures in JavaScript, unlike most javascript developers who fear it and find it complicated.

Not just we should know how to present the concept of closure in interviews, but also how to implement its use case in our code. 😎


I hope you enjoyed this article and got closer to Closure. Haha! 😁

I would be happy to have your feedback. Until then Happy coding and love JavaScript. Stay tuned, and see you in the next article πŸ˜‰, Part 2 of getting closer to Closure.

Β