JavaScript closures πŸš€

March 11, 2021

Closures

Closures are functions that close over their lexical environment or their scope. This allows us to access an outer function scope from an inner function. We use closures in many different places. For example, if we are filtering an array of items, or if we are creating a timeout.

Closure scope chains

A closure is a feature in JavaScript where an inner function has access to the outer (enclosing) function’s variables β€” a scope chain.

πŸ‘‰ The closure has three scope chains:

  • it has access to its own scope β€” variables defined between its curly brackets {}
  • it has access to the outer function’s variables
  • it has access to the global variables

Closure example

Let’s have a look at a simple closure example in JavaScript:

function outer() {
   let apples = 10;
   function inner() {
        
         let plums = 20; 
         console.log(plums+apples);
    }
   return inner;
}

Here we have two functions:

  • an outer function outer which has a variable b, and returns the inner function
  • an inner function inner which has its variable called a, and accesses an outer variable b, within its function body

The scope of variable b is limited to the outer function, and the scope of variable a is limited to the inner function.

πŸ‘‰ Let us now invoke the outer() function, and store the result of the outer() function in a variable X. Let us then invoke the outer() function a second time and store it in variable Y.

function outer() {
   let  apples = 10;
   function inner() {
        
         let plums = 20; 
         console.log(plums+apples);
    }
   return inner;
}
var X = outer(); //outer() invoked the first time
var Y = outer(); //outer() invoked the second time

πŸ‘‰ What happens when the outer() function is first invoked? πŸ€”

Variable apple is created, its scope is limited to the outer() function, and its value is set to 10. The next line is a function declaration, so nothing to execute. On the last line, return inner looks for a variable called inner, finds that this variable inner is actually a function, and so returns the entire body of the function inner.

πŸ›‘ Note that the return statement does not execute the inner function β€” a function is executed only when followed by () β€” , but rather the return statement returns the entire body of the function.

The contents returned by the return statement are stored in X. Thus, X will store the following:

function inner() {
let plums=20;
console.log(plums+apples);
}

πŸ‘‰ Function outer() finishes execution, and all variables within the scope of outer() now no longer exist.

This last part is important to understand.

πŸ›‘ Once a function completes its execution, any variables that were defined inside the function scope cease to exist.

The lifespan of a variable defined inside of a function is the lifespan of the function execution.

What this means is that in console.log(plums+apples), the variable apples exists only during the execution of the the outer() function. Once the outer function has finished execution, the variable apples no longer exists.

πŸ›‘ When the function is executed the second time, the variables of the function are created again, and live only up until the function completes execution.

Thus, when outer() is invoked the second time:

A new variable apples is created, and its scope is limited to the outer() function, and its value is set to 10.

The next line is a function declaration, so nothing to execute.

return inner returns the entire body of the function inner.

The contents returned by the return statement are stored in Y.

Function outer() finishes execution, and all variables within the scope of outer() now no longer exist.

πŸ‘‰ The important point is that when the outer() function is invoked the second time, the variable apples is created anew. Also, when the outer() function finishes execution the second time, this new variable apples again ceases to exist.

This is the most important point to realize is that:

πŸ›‘ The variables inside the functions only come into existence when the function is running, and cease to exist once the functions completes execution.

Let's look at Xand Y. Since the outer() function on execution returns a function, the variables X and Y are functions.

This can be easily verified by adding the following to the JavaScript code:

console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function

Since the variables X and Y are functions, we can execute them.

A function in JavaScript can be executed by adding () after the function name, such as X() and Y().

function outer() {
let apples = 10;
   function inner() {
        
         let plums = 20; 
         console.log(plums+apples);
    }
   return inner;
}
let X = outer(); 
let Y = outer(); 
//end of outer() function executions
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third time
Y(); // Y() invoked the first time

When we execute X() and Y(), we are essentially executing the inner function. Lets examine what happens when X() is executed the first time:

πŸ‘‰ Variable a is created, and its value is set to 20.

πŸ‘‰ JavaScript now tries to execute plums + apples. Here is the interesting part.

JavaScript knows that plums exists since it just created it. However, variable apple no longer exists.

πŸ›‘ Since apples is part of the outer function, apples would only exist while the outer() function is in execution. Since the outer() function finished execution long before we invoked X(), any variables within the scope of the outer function cease to exist, and hence variable apples no longer exists.

This can be handled with closures

πŸ‘‰ The inner function can access the variables of the enclosing function due to closures in JavaScript.

The inner function preserves the scope chain of the enclosing function at the time the enclosing function was executed, and thus can access the enclosing function’s variables.

In our example, the inner function had preserved the value of apples=10 when the outer() function was executed, and continued to preserve (closure) it.

It now refers to its scope chain and notices that it does have the value of variable b within its scope chain, since it had enclosed the value of b within a closure at the point when the outer function had executed.

Thus, JavaScript knows plums=20 and apples=10, and can calculate plums+apples. You can verify this by adding the following line of code to the example above:

function outer() {
let apples = 10;
   function inner() {
        
         let plums = 20; 
         console.log(plums+apples);
    }
   return inner;
}
let X = outer(); 
console.dir(X); //use console.dir() instead of console.log()

It is clear that the inner function has three scope chains:

access to its own scope β€” variable plums

access to the outer function’s variables β€” variable b, which it enclosed

access to any global variables that may be defined

function outer() {
let apples = 10;
let bananas = 60;
   function inner() {
        
         let plums = 20; 
         console.log("plums= " + plums + " apples= " + apples);
         plums++;
         apples++;
    }
   return inner;
}
let X = outer();  // outer() invoked the first time
let Y = outer();  // outer() invoked the second time
//end of outer() function executions
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third time
Y(); // Y() invoked the first time

πŸ—―οΈ Suggestions for improvements are welcome in the comments.

Up next