DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Learn Closure in JavaScript with examples in < 16 mins
Emmanuel Fordjour  Kumah
Emmanuel Fordjour Kumah

Posted on

Learn Closure in JavaScript with examples in < 16 mins

There is a popular programming term closure, that JavaScript developers should know. Unfortunately, most developers struggle to get their heads around this concept.

In this article, I will explain Closure in clear terms using simple-to-understand examples.

To dive into Closure, it is essential we understand the following concepts:

  • Scope
  • Lexical Environment
  • Functions returning Functions

Let's get started

Scope

Consider the analogy below:

  • In our homes, there are items that are **accessible by all **members of the household. For instance, the remote control of the TV set.
  • However, there are some personal or specific items that are only accessible to you, and you can restrict anyone from using those items. An example is your personal toothbrush or your mobile phone.

The above is essentially how scope works in JavaScript.

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

When we define a variable, we determine the part of our code that variable should be visible and accessible. This permits the variable to exist within some boundaries, outside of these boundaries, the variable is inaccessible.

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.

If a variable is defined in the global scope, it is accessible anywhere in your code (including functions, objects, etc.)

See the code below :

let user = "Emmanuel";  // global scope

// any code below can access the user variable

function greetUser() {
// code here can also access the user variable
return user;
}
Enter fullscreen mode Exit fullscreen mode

In the code above:

  • The user variable is declared outside of the greetUser function giving it a global scope.
  • Any code we write can access the user 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.

See the code below:

// code here cannot access the user variable

function greetUser(){
let user ="Emmanuel"  // user is a Local variable or has a local scope
//  any code here, have access to the user variable 
console.log(user)
}
//code here cannot access the user variable.
Enter fullscreen mode Exit fullscreen mode
  • In the code above, the user variable is declared inside the greetUser function hence, has a local scope.
  • Any code above or below the greetUser function cannot access the user variable
  • Aside from the body of the greetUser function, If you try accessing the user variable, you will get the ReferenceError: user is not defined

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

See the examples below:

function isLocal(){
  const  user ="Emmanuel"
  console.log(user)
}

isLocal() // execute the function

console.log(`Where is ${user}`) // error 
Enter fullscreen mode Exit fullscreen mode

The output of the code will be

  • Emmanuel
  • ReferenceError: user is not defined

The error is displayed because the user 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.

See the code snippet below

function greetUser(){
let user ="Emmanuel"  // user is a Local variable
//  any code here, have access to the user variable 
console.log(`Hi there ${user}`)
}

function sayHello(){
    let user="Emmanuel" // same variable name used in the sayHello function
    console.log(`Hello ${user}`)
}

//call both functions
greetUser()

sayHello()
Enter fullscreen mode Exit fullscreen mode

The output of the code below will be :

Hi there Emmanuel
Hello Emmanuel
Enter fullscreen mode Exit fullscreen mode

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.

For 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

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

JavaScript Scope

Let's now explore the concept of nested function.

Nested Function

A nested function is a function defined inside another function.

See the code below:

// Example 1
function getUserDetails(fName, lName){

// greetUser function is created inside the parent function

    function greetUser(fName,lName){
        return (`Hello ${fName} ${lName}`)
    }

//Example 2
function increaseCount(){
let count = 0;

// newCount is a nested function
return function newCount(){
return count++
}
}
Enter fullscreen mode Exit fullscreen mode

greetUser, 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 greetUser function is nested in the getUserDetails function. This permits the greetUserfunction 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.

Let's explore why the above is possible in the next topic.

Lexical environment

During code execution, the JavaScript engine creates a special internal object called Lexical environment.This Lexical environment has an environment record which is an object created to store all local variables.

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

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

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

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.

Lexical Scoping (Accessing variables declared in the outer scope)

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. *

To rephrase, an inner or block scope can** "look outside"** to access variables declared in the global scope.

See the code below:

let x = 20; // global scope

function sumNum(){
// inner scope
  let y = 10;
  let z= 40;
  console.log(x+y+z)
}

//execute the function.
sumNum()
Enter fullscreen mode Exit fullscreen mode

The output of the code will be 70. Do you know why?

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.

When the code above is executed:

  • 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 *

Take a look at this other example

function outer(){
  let user="Emmanuel"; //local scope

// inner function

  function init(){
    console.log(user); 
  }  
  init()
}

//execute the outer function
outer()

Enter fullscreen mode Exit fullscreen mode

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.

See the code below

function outerFunction(){
  return function innerFunction(){
    //code goes here
  }
}

//return inner function and assign to a variable
const useFunction = outerFunction();
//useFunction stores the innerFunction
Enter fullscreen mode Exit fullscreen mode

Take a look at this other example

function outer(){
  let a = 10
  return inner(){
    //code goes here
    let b = 20;
    console.log(a + b)
  }
}
//return inner function 
const getInner = outer()
Enter fullscreen mode Exit fullscreen mode

Here we have two functions

  • 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.

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

const callInner = mainFunction("Emma"); // execute mainFunction
console.log(callInner())
Enter fullscreen mode Exit fullscreen mode

The output of the code above will be Hello Emma.

Let's explore the details:

  • 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 Emma.

JavaScript closure

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.

Examine the code below:

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("Emmanuel"); 

// 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)
Enter fullscreen mode Exit fullscreen mode

The output of the code will be

Hi Emmanuel,Your article on Closures is receiving a lot of views, Congrats !!!
Enter fullscreen mode Exit fullscreen mode

Let's walk through the code step by step

  1. We access the button element defined in our html and assign it to btn variable
  2. We define the composeMail and emailContent functions.
  3. The composeMail accepts a parameter senderName which when executed, will receive the name of the user to send the email.
  4. When the composeMail is executed, it returns the emailContent function which we assign to the readMail variable to be executed later.
  5. 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.
  6. On execution of the composeMail function, the returned function is assigned to readMail
  7. 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.
  8. Finally, we define the viewInbox function and in the body of the function we execute the emailContent.
  9. Now, on click of a button, the viewInbox function is executed, and the emailContent function still has access to the senderName variable.
  10. We can now say the emailContent function is a closure.

Eg.2 Which variables are accessible?

Which of the timeOfDayvariables is accessible, in the code below?

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("Emmanuel");

// call the returned function
greeting(); // what will it show?
Enter fullscreen mode Exit fullscreen mode

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?

Result:

"Good evening Emmanuel, what would you like to do today"
Enter fullscreen mode Exit fullscreen mode

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 Emmanuel, 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

Let's wrap up with another example:

const lessonNum = 2; //global variable
const learnFruit = (fruit) => {
  const fruitName = fruit; // variable in the outer scope

//inner function

  return getFruit = (fruit)=>{
    let userName ="Emmanuel"; //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()
Enter fullscreen mode Exit fullscreen mode
  • Executing learnFruit returns the inner function getFruit which is a closure.
  • The getFruit has access to three scope chains:
  1. The lessonNum variable in the global scope
  2. The fruitName variable in the outer scope
  3. 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

In 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.

I would love to read your input on this article, kindly leave a comment below. Don't forget to follow me on Twitter, and share this article to help others

Top comments (1)

Collapse
 
fruntend profile image
fruntend

Π‘ongratulations πŸ₯³! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up πŸ‘

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.