Closures very popular topic of javascript and every developer should know. We will go through each concept related to closure and understand closure with examples.


Before understanding closure, we should know these concepts:


  • Scope
  • Lexical Environment
  • Functions returning Functions


Scope:


Let's understand with a real-life example:


  • In our homes, there are items that are accessible by all members of the house. For instance, the remote control of the TV set or dining table.
  • However, there are some personal or specific items that are only accessible to you, and you can prevent others from using those items. Consider your personal toothbrush or mobile phone.


The above example is essentially how scope works in JavaScript.


The scope is "access". It permits a developer to restrict access to certain variables in specific areas.


When we define a variable, we specify where in our code it should be visible and accessible. This allows the variable to exist within some boundaries; however, the variable is inaccessible outside of these boundaries.


Every variable has a scope, If a variable is not in the current scope, it will not be available for use.


JavaScript has the following kinds of scopes:

  • Global Scope
  • Local or function Scope
  • Block Scope


Global Scope:


For variables declared outside of any function or curly braces {}, the scope is the whole program (you can access such variables wherever you want). These are called global.


Example :

let name = "the Coding dev";  

// global scope


// any code below can access the name variable


function getUser() {

// code here can also access the name variable

return name;

}


Code Explanation:


  •  The name variable is declared outside of the getUser function giving it a global scope.
  • Any code we write can access the name variable.


Local or function Scope:


We can declare variables inside of a function. When variables are declared within a function, they are only accessible within the function. These variables are said to keep a local scope.


Example:


// code here cannot access the name variable


function getUser(){

let name ="the coding dev"  

// name is a Local variable or has a local scope


//  any code here, have access to the name variable 

console.log(name)

}

//code here cannot access the user variable.


Code Explanation:


  • In the name variable is declared inside the getUser function hence, has a local scope.
  • Any code above or below the getUser function cannot access the user variable
  • Aside from the body of the getUser function, If you try accessing the user variable, you will get the ReferenceError: name is not defined


Local variables get created when a function is declared and deleted when the function is executed.


Example:


function isLocal(){

  const  name ="the coding dev"

  console.log(name)

}


isLocal() 

// execute the function


console.log(`Where is ${name}`) 

// error 


Output:

the coding dev

ReferenceError: name is not defined


The error is displayed because the name variable is deleted when the isLocal() function is executed.


Because local variables are only accessible inside the body of the function, variables with the same name can be used in different functions.


Example:


function getUser(){

let name ="The coding dev"  

// name is a Local variable


//  any code here, have access to the name variable 

console.log(`Hi there ${name}`)

}


function sayHello(){

    let name="Rahul" 

    // same variable name used in the sayHello function


    console.log(`Hello ${name}`)

}


//call both functions

getUser()


sayHello()


Output:

Hi there The coding dev

Hello Rahul



Block Scope:


Prior to ES6 (2015), JavaScript had only Global and Function Scope. The introduction of the let and const keywords in ES6 provided Block Scope in JavaScript.


Variables declared within a code block or curly braces {...} have block scope, and cannot be accessed from outside the block.


Example:

{

 // do some tasks with local variables that should not be seen outside


  let message = "Hello"; 

  // only visible in this block


  alert(message); // Hello

}

// message can NOT be used here

alert(message); 

// Error: message is not defined


We can use code blocks to separate a piece of code that does its own task, with variables that only belong to it.


Nested Function:


A nested function is a function defined inside another function.


Example 1:


 function getUserDetails(fName, lName){


    // getUser function is created inside the parent function


    function getUser(fName,lName){

        return (`Hello ${fName} ${lName}`)

    }

}


Example 2:


function increaseCount(){

    let count = 0;


    // newCount is a nested function

    return function newCount(){

        return count++

    }

}


getUser, and "newCount" are nested functions because they are declared inside another function.


  • A nested function can be returned, either as a property of a new object or as a result by itself.
  • The returned value can then be used somewhere else. Regardless of where it is used, it still has access to the same outer variables.
  • In a nested function, we might have variables in the outer scope as well as the inner scope.
  • The variables of the outer scope are accessible inside the inner scope.


In the examples above, the getUser function is nested in the getUserDetails function. This permits the getUser function to access the fName and lName variables passed as parameters in the outer function, getUserDetails.


Likewise, the newCount function can access the outer variable count.


Lexical environment:


The JavaScript engine creates a special internal object called the Lexical environment during code execution. This Lexical environment includes an environment record, which is an object that was created to store all local variables. 


The lexical environment will be pre-populated with all declared variables in the code.


Prior to the execution of a function, the lexical environment provides the environment record to store the parameters and or variables declared in the function body.


If a variable is declared in the global scope, a lexical environment is also created to store the variable in the environment record.


Example:


let x = 20; // global scope


function sumNum(){

// inner scope

  let y = 10;

  let z= 40;

  console.log(x+y+z)

}


//execute the function.

sumNum()



Like the example above, when you declare a global variable and a function, you have two lexical environments respectively:

 

  • Outer environment
  • Inner environment
  • The sumNum is the inner environment whilst the x variable forms part of the outer environment.


Accessing variables declared in the outer scope - Lexical Scoping


Lexical scoping describes how the JavaScript engine determines variable names when functions are nested.


The term "lexical" relates to the fact that lexical scoping uses the location where a variable is declared within the code to determine where that variable is available.


Consider a scenario where you live in a confined house with a lower fence wall. Within the house, you can look outside the fenced wall to have visibility of your environment (and access stuff outside the fenced wall).


Likewise in JavaScript, each scope can "look outside" to view the outer scope and access variables in the outer scope. Furthermore, nested functions can access variables declared in the outer scope.


A variable defined in the global scope can be accessed inside the inner scope or any block scope even when it is not declared in the block scope. 


Accessing a variable in the lexical environment


When a code wants to access a variable, the environment record of the internal environment is searched first, followed by the record in the outer environment, then the outer one, and so on, until it gets to the global scope. If the variable is not found anywhere, an error will be thrown.


Code Explanation:


  • The y and z variables are instantly found in the environment record of the internal environment of the sumNum function.
  • However, when the code wants to access the x variable, it cannot find the x variable declared in the environment record of the inner environment.
  • It "looks outside" the inner environment and searches the outer environment. It then finds the outer environment in the global scope and accesses the value stored in the x variable.
  • Hence console.log(x + y + z) produces the expected output of 70.


In effect, a block or function scope can look at its inner environment for a variable, if not found, it checks the outer environment for the variable, and if it was declared there, use the value declared in the outer environment. This concept is known as lexical scoping. 


Example:


function outer(){

  let user="The Coding Dev"; //local scope


// inner function

  function init(){

    console.log(user); 

  }  

  init()

}


//execute the outer function

outer()


outer() creates a local variable user and a function init which is an inner function. 

Note that the init() function has no declared variables. However, since inner functions have access to the variables declared in the outer functions, init() can access the user variable declared in the parent function, outer().


Function Returning a Function:


In a nested function, when the outer function is executed, it can return an inner function. Because functions are values, the inner function can be assigned to a variable and executed later in our code.


Example:


function outerFunction(){

  return function innerFunction(){

    //code goes here

  }

}


//return inner function and assign to a variable

const useFunction = outerFunction();

//useFunction stores the innerFunction


Example:


function outer(){

  let a = 10

  return inner(){

    //code goes here

    let b = 20;

    console.log(a + b)

  }

}

//return inner function 

const getInner = outer()


Code Explanation:


  • An outer function,outer which has a variable a, and returns an inner function inner
  • The body of the inner function, we declare a variable b and assign it a value of 20.
  • The inner function can have access to the outer variable a.
  • The scope of the variable a is limited to the outer function, whilst the scope of the variable b is limited to the inner function.
  • We then execute the outer function

Keep in mind, anytime we execute a function, any variables defined in the function scope cease to exist.

In effect, in the console.log(a+b), the variable b exists prior to the execution of the outer function.

Once the outer function has been executed, the b variable, **no longer exists.

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

Variables of a function are recreated anytime the function is called, and live up until the function completes execution.

The question is, what happens to variables when the function call that created them has completed its execution?

This question introduces the concept of closure.


Closure


From the above, we know that variables get deleted when the function that created them has completed its execution.

Hence, it should not be technically possible to access the b variable in the inner function.

That said, the inner function can access the variables of the surrounding function due to closures in JavaScript.

A closure gives you access to an outer function’s scope from an inner function.

When an inner function is called, the function body remembers the environment in which it was created, not the environment in which it is called.

Take note of this phrase, "the environment in which it was created". Because the inner function knows the place of birth ( environment in which it was created), the inner function preserves the scope chain of the surrounding function at the time the surrounding function (outer function) was executed.

Even though you will expect the variables declared in the outer function to be deleted after it has been called, the inner function has already accessed and preserved the variables in the outer function.

The inner function then carries the preserved variables along with it (everywhere it goes)

The following code shows an example of this. It defines a function, mainFunction, that creates a local variable. It then returns a function that accesses and logs this local variable to the console.

Example:

function mainFunction(name){
  let user= name //local variable
  return function innerFunction(){
    console.log(`Hello ${user}`)
  }
}

const callInner = mainFunction("Rahul"); 
// execute mainFunction

console.log(callInner())


The output of the code above will be Hello Rahul.


Code Explanation:

  • When the outer function mainFunction is executed, it returns an inner function that is assigned to the calInner variable
  • This inner function remembers the environment in which it was created and hence it is able to access and preserve any variable in that environment.
  • On execution of the mainFunction() we would expect the local variable user to get deleted. But note that, prior to its execution, the innerFunction was able to access and preserve the user variable declared in the outer scope.
  • No matter where the innerFunction is called, we are still able to remember the variable it has preserved even when the mainfunction has finished execution. The innerFunction is a closure.
  • Executing the callInner() function, therefore, produces the output Hello Rahul.


Examples of closures


In this example, we want to create a function that allows us to send emails to users, the user reads the content of the mail when he clicks on the button.


To achieve this, we declare two functions composeMail and emailContent.


  • composeMail will accept the senderName as a parameter
  • In the body of the emailContent we write the content of the email to send.
  • Because these two functions are related, we will nest them, with the composeMail as an outer function and emailContent as an inner functon.
  • The emailContent will be a closure, it is able to look outside its scope and access and preserve any variable or parameter defined in the outer scope.


We would also define a click event listener, that listens for a click of a button to enable us to read the content of the email.


Example 1:


const btn = document.getElementById("btn") 

// access a button element with id "btn"


// define composeMail and emailContent functions


const composeMail = (senderName)=> {


// nested function

  const emailContent = (content)=> {

    return `Hi ${senderName},${content}`

  }

  return emailContent

}


// execute the composeMail

const readEmail = composeMail("Rahul"); 


// view content of mail

function viewInbox(){

  console.log(readEmail(`Your article on Closures is receiving a lot of views, Congrats !!!`));


}


// add a click event listener

btn.addEventListener('click',viewInbox)


Output:

 Hi Rahul,Your article on Closures is receiving a lot of views, Congrats !!!


Code Explanation:


  • We access the button element defined in our html and assign it to btn variable
  • We define the composeMail and emailContent functions.
  • The composeMail accepts a parameter senderName which when executed, will receive the name of the user to send the email.
  • When the composeMail is executed, it returns the emailContent function which we assign to the readMail variable to be executed later.
  • Prior to the execution of the composeMail function, the emailContent is able to access and preserve any variable declared in the composeMail .For this instance, it will be senderName.
  • On execution of the composeMail function, the returned function is assigned to readMail
  • The emailContent function has safely stored the senderName variable hence anywhere we execute the emailContent we still have access to the value passed to the composeMail during execution.
  • Finally, we define the viewInbox function and in the body of the function we execute the emailContent.
  • Now, on click of a button, the viewInbox function is executed, and the emailContent function still has access to the senderName variable.
  • We can now say the emailContent function is a closure.


Example 2:


function greetUser(name) {

  let timeOfDay = "Good evening";


//define another function

  return function() {

    console.log(`${timeOfDay} ${name}, what would you like to do today`);

  };

}


let timeOfDay = "Good morning"; // change time of day


// run the greetUser function

let greeting = greetUser("Rahul");


// call the returned function

greeting(); 

// what will it show?


In the example above, the function greetUser has a local variable timeOfDay, it also creates another anonymous function. That new function can be called somewhere else in our code.


In the global scope, we also declare another timeOfDay variable and assign a value to it.


When we execute the return function, which of the timeOfDay variable will it access, and what would be the final output?


Output:


"Good evening Rahul, what would you like to do today"


Code Explanation:


  • Regardless of where this new function is called, it will have access to its outer variable from its creation place, and not the invocation place. Creation refers to where the function was first defined ( place of birth)
  • The value of the timeOfDayvariable at its place of birth Good evening and not Good morning., hence the output.


Note that, if there were no timeOfDay variable in the greetUser function, then we would search outside that function, for it, and it would be found in the global scope.

In that case, the value of the variable will be:Good morning. Hence the result would be Good morning Rahul, what would you like to do today.


The ability to reference a specific instance of a local variable in a surrounding scope is called closure.


A function that references variables from local scopes around it is called a closure.


This action not only frees you from having to worry about the lifetime of variables but also makes it possible to use function values in unique methods.


The Scope Chains in Closure


In closure, the inner function has three scope chains:


  • Access to its own scope: variables declared between its curly braces {}
  • Access to the outer function’s variables: variables declared outside the inner function
  • Access to any global variables that may be defined


Example:


const lessonNum = 2; //global variable

const learnFruit = (fruit) => {

  const fruitName = fruit; // variable in the outer scope


//inner function


  return getFruit = (fruit)=>{

    let userName ="Rahul"; //inner variable

    console.log(`Hello ${userName},welcome to lesson number ${lessonNum},here you learn about fruits.The fruit you picked is ${fruitName},and it begins with the letter ${(fruitName.charAt(0)).toUpperCase()}`)

  }

}


const sayAlphabet = learnFruit('mango');


// execute the inner function

sayAlphabet()


  • Executing learnFruit returns the inner function getFruit which is a closure.
  • The getFruit has access to three scope chains:

The lessonNum variable in the global scope

The fruitName variable in the outer scope

The userName variable in the inner scope


To reiterate the concept of closure, once the learnFruit() has been executed, we would have expected the variable declared in its scope to be deleted.


However, prior to execution, the getFruit function is able to access and keep copies of the outer variables.


Regardless of where the getFruit is run, we can still use any variables defined in the scope chain.


Rule of Thumb

A rule of thumb to identify a closure: if inside a function you see a strange variable (not defined inside that function), most likely that function is a closure. Because the strange variable has been captured and preserved.


Importance of closures


Closures permit you to save a snapshot of the scope where the function was initially declared.


Because they exist within outer functions, it allows you to run the outer function, and execute the inner function (closure) later, with the values from the outer function saved.


This is optimal than calling two different functions if these two functions are linked because it empowers you to write clean and maintainable code.


Summary


Closure is like keeping a copy of the local variables of the function.


It helps eliminate the duplication of effort with a program or to hold values that need to be reused throughout a program so that the program doesn't need to recalculate the value each time it's used.