How Closure works in JavaScript

How Closure works in JavaScript

So many talks about closures in JavaScript. If you are in the beginning stage of learning JavaScript, you may have a hard time wrapping your head around this concept which sounds terrifying. The challenge in learning a programming language is that most of the time you cannot grasp the meaning of a concept unless you are already familiar with other concepts which may have the same level of complexity. That being said, to understand Closure in JavaScript, we already need to know the concept of Scope and Function in Javascript. Fortunately these concepts are the basic elements of any language and students get over them before they encounter more complex concept like closures. There is just one more step to take before understanding Closure and that is to explain how exactly Scope and Function work in JavaScript.

I will start by showing how JavaScript defines scope using a simple example and build on the same example to explain Closure.

As we know, JavaScript has two types of scopes: global scope and local scope. Global Scope refers to a part of memory that is available to every script and function on the application. Unlike global scope, local scope can only be accessed within the function where it is created.

var g = 100;

function doubleX(x) {

   var y = 2;

   return x*y;

}

doubleX(2);

console.log(g) // returns "100"

console.log(y) //returns "ReferrenceError: y is not defined"

Here, x belongs to the global scope, while y belongs to the local scope, so it cannot be accessed outside doubleX function. It is not even known to the scripts outside the function; hence the error message.

When JavaScript engine reaches the function call line, it allocates enough space for all the variables inside the function; i.e. x and y. As soon as the engine is done with the function, i.e. when it reaches the closing curly bracket, it removes x and y from the stack and they are not accessible anymore, as they exist no more.

Now suppose that we want to make our function more flexible and give the user this option to enter a muliplier(y), in addition to multiplicand(x).

One option is to add an extra argument to doubleX function and rewrite it as follows:

 function doubleX(y, x) {

   return x*y;

}

The algorithm for this function is as simple as below:

1. set y value

2. set x value

3. return x*y

Notice that both x and y are considered as local variables, though they are not exactly inside the curly brackets.

But what if we want to initialize the multiplier(y) on the fly? Suppose that we have a switch statement and we want the application sets the value of y based on the user profile and then asks the user to set the value for x. In this case, x and y should not be set at the same time, while the above code puts them in a bundle which requires setting them at one shot.

Wouldn't it be good that instead of returning the immediate result of x*y, the function returns another function which has the value of y already set and then runs the last 2 steps of the algorithm within the returning function? This is called currying. Currying is the process of taking a function with multiple arguments and returning a series of functions that take one argument and eventually resolve to a value.(1)

In our example, to curry doubleX function, we can re-write it as follows:

function multiplyX(y) {

   return function(x) {

      return x*y   

   }   

}

const doubleX = multiplyX(2);

console.log(doubleX(6))// returns 12

 If you have worked with Object Oriented languages, you may think that there is something wrong with this code and you would bet that it won't work. You would also advise me to learn more about how a programming language operates on the memory side to run a function. Every programming language starts with allocating a part of the memory block as its working station which is called stack memory.

Stack memory is basically a data structure which can contain different stack frames. Each block of code (codes between two curly brackets) runs on its own stack frame. These frames are stored on top of each other. The engine only can work on a single block at a time which is logically the topmost one. When a block of code is fully read and run, the containing stack frame is deleted so that the engine can read the next frame below it.

When it runs a function, it saves the primitive values (static data) of all variables in the block in the stack and saves the values of all objects (dynamic data) in another type of memory which is called heap space. The reason for this distribution of responsibilities between these two types of memory is beyond this article.

So when our JS engine reads const doubleX = multiplyX(2), this is how it operates on the memory:

1. it allocates some memory to doubleX function in the heap space.

2. it creates a frame to run multiplyX(2) on the stack.

3. in the same frame, it allocates some memory to y as it is an argument with a primitive type.

4. the result of this frame is a function of type object which will be referred by doubleX.

5. the engine closes the multiplyX frame so the value for y is cleared as it is a local variable to that function.

Now the JS engine goes to the next line. It reads console.log(doubleX(6)). This is what happens in the memory (apart from the console.log script) :

6. it creates a new stack frame to run doubleX function.

7. it stores the value of x in the stack as it of primitive type.

8. now it needs the value for y to return x*y.

But the value of y is already cleared when the engine cleared the previous frame. So how does it successfully return the correct answer and not an error?

The answer is by using Closure. "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."(2)

So here, our closure is a bundle of the returning function in multiplyX and the state of mulitplyX instance which includes the value of y (i.e. 2). If you read step 4 in the run process one more time, it says that engine assigns the returning function as a value to doubleX. If we debug the application on the browser inspector, we see that this value is the outer function, here mulityplyX, not a new object which only contains the returning function. That is the whole concept of closure. When the engine runs the inner function (here doubleX(2)), it actually runs multiplyX function (outer function). If we check what happens in the scope part of the debugger, it shows that there are two local scopes for mulitplyX where x is saved on one and y is on another. The point is that the second time that the engine runs multiplyX function, it has access to two local variables. This is the state part which is mentioned in the closure definition.

Knowing exactly what happens when JavaScript works with closure needs more in-depth knowledge of JavaScript engines and the way they manage memory. In this article, I tried to convey my understanding of how closure works based on other concepts such as scopes and memory types. I don't claim it is a complete picture, though I hope that it gives you at least the basic idea of how this concept is related to other concepts in JavaScript.


Sources:

Currying in JavaScript (https://wsvincent.com/javascript-currying)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

要查看或添加评论,请登录

Mojtaba F.的更多文章

  • Rest Parameters and Spread Syntax

    Rest Parameters and Spread Syntax

    In this article, I want to talk about the famous three-dots in JavaScript. Depending on the context it is used, it…

  • Array Loop in JavaScript

    Array Loop in JavaScript

    Arrays in JavaScript are a special kind of object. The main difference between an array and a regular object in…

  • How Does Function Chaining Work in JQuery?

    How Does Function Chaining Work in JQuery?

    JavaScript is all about manipulating the DOM and, to this end, it needs to access the elements in the first place…

  • 3 Amazing New Features in ES2021

    3 Amazing New Features in ES2021

    EcmaScript specifications yearly update for 2021 is no slouch. Here I will introduce you with 3 highlight features…

社区洞察

其他会员也浏览了