Now, we will understand all the basic concepts like how the function works, how a function can be declared inside another function using the scope chain, returning the function from the function, through the examples, then will understand the closure concept.
Example 1: Here, we are simply illustrating the working of function as shown below:
- First of all, a global execution context will be created and then the function definitions will get space in memory.
- Then the thread of execution phase comes into the picture and variable x gets the value 5 and in the next line console.log prints the value of x, and finally when function execution finishes, it gets removed from the call stack and execution context in which variable x was stored gets destroyed, the same happens for function b().
- But here we can’t access the variables x and y outside the block, because when the function finishes its execution context, an execution context also gets deleted and when the x is gone, it is nowhere in the memory. Same for function b(), we can’t access the var y outside that function.
Example 2: In this section, we will discuss the function inside the function using a scope chain to access the variable.
- Here an execution context gets created and then function a() gets space inside the memory.
- After then we call the function a(), so it gets its own execution context.
- There is again a variable x and function b() which gets a space in memory and later during the code execution phase, variable x gets printed
- As soon as control goes to call of function b(), this gets another execution context inside the last execution context and the var y gets space in memory after then it encounters a console.log(x) but x is not in the execution context of function b() so it goes to the parent lexical environment because of scope chain and hopefully finds it there and prints the value of x,
- The var y is something that is inside the function hence gets printed without any extra effort.
Example 3: In this case, we will discuss the returning function from the function.
- Global Execution context created, the result and function a() gets allocated memory space.
- Later in the call of function a(), another execution context gets created and function b() gets allocated in that memory space.
- Finally, after printing a line and return a function, a() finishes its execution and removes it from the call stack. Also, its execution context gets deleted.
- The result of function a() gets stored in the const variable.
- We have successfully called the function result() and as a consequence, the functionality inside function b() which was returned gets executed.
Example 4: Here comes the closure part. Let’s say you don’t only have a simple console.log but some variables are also declared inside the function b(). Now, just stop here & think about the output of the code.
- Global Execution context created, variable result and function a() gets space inside the memory.
- During the thread execution phase, first of all, a() function gets called.
- A new execution context gets created var x and function b gets space in memory, and just after it returns the function b which means we have the result as the code of the function, and now the function a() gets removed from the call stack also it’s execution context destroyed.
- In the last line, when we call function result(), it successfully starts executing the function code inside it. But as soon as it encounters a variable that is not inside the execution context of that function, so it tries to find out the var x in the outer scope but wait, we don’t have access to the execution context of function a (), so how does this x will be executed?
- Does it mean that var x will become undefined? No, because this is the concept of closure whenever we expose or return the function from another function, not only the code being returned but it comes along with a special thing called the lexical environment, which means the surrounding environment of its outer parent function.
- So the function b() when returned from the function, it comes along with the lexical environment of its parent hence always has access to the reference of variable x. Note the last line “always have access to the reference of variables” not the value.
Definition: A closure is the combination of functions bunched together with its lexical environment. The lexical environment is the local function memory & the reference to the lexical environment of a parent. In other words, closure is created when a child function keep the environment of the parent scope even after the parent function has already been executed.
This reference to the lexical environment of a parent is the reason why closure functions have access to the variables of outer functions even if those functions are not in the call stack or we can say even if the outer functions are closed.
How closures are created: Closure gets created whenever some function is being created at the time of the creation of another function. In the above example, the function inner is being created at the time of the creation of function a().
Few Usecases & Examples of Closures:
1. Privacy: Whenever we have to hide some functionality or variable as encapsulation, we wrapped that thing inside the lexical environment of the parent.
Example: The below example is a basic template of encapsulation with closures.
- All the global execution context will be created in the memory allocation phase, the function outer() & the const closure function will get space in memory.
- At the time of thread execution, the code inside function outer will starts executing because it is the first line in code.
- Now another execution context will be created and the const notAccessibleByAnyone will get space and later the outer functions return the inner function code and the execution context will vanish out. Notice that the const variable is not in the execution context.
- But closure function has the reference to that variable, so this is the whole concept of encapsulation with closures.
2. Partial Functions: When we have to create some kind of functionality in which there exists a common pattern then we create a parent function that takes few parameters and create an inner function that takes fewer parameters than the parent. This concept is also called higher-order functions in which we return functions from function.
Example: The below example provides a basic illustration of how this partial function looks like.
- Here we have created a parent function that receives a multiplier as a parameter and then passes it to an inner function.
- Later when we invoke the parent function it returns the code of the inner function which always have the access to that multiplier provided during the parent function call.
- The function multiplyBy2() holds a function that takes an array and runs for loop on it later returns the modified array in which elements are being multiplied by 2.
- The function multiplyBy4() holds a function that takes an array and runs for loop on it later returns the modified array in which elements are being multiplied by 4.