DEV Community

Cover image for JavaScript Closures Explained with Examples
Oluwatobi Sofela
Oluwatobi Sofela

Posted on • Originally published at codesweetly.com

JavaScript Closures Explained with Examples

A closure is an inner function referencing one or more items in its lexical scope (definition area).

Note: An inner function is a function defined inside a block, module, or another function.

In other words, a closure is a function that retains access to its lexical scope's features even after its execution closes.

For instance, consider the following code:

function createProfile() {
  const myName = "Oluwatobi";
  function showName() {
    return myName;
  }
  return showName();
}
createProfile();
Enter fullscreen mode Exit fullscreen mode

Try Editing It

showName() is the closure in the example above because:

  • It is an inner function.
  • It retains a reference to its lexical scope's variable.

Note:

  • The createProfile() function's body is showName()'s lexical scope.
  • The showName() function retained a reference to its lexical scope's state (variable's value) by calling myName.
  • The reason showName() is a closure is not because it was returned nor invoked within createProfile(). Instead, it is a closure because it is an inner function that retains a reference to its lexical scope's feature (that is, myName).
  • All closures have three scopes:
    • Local scope: The space within the closure.
    • Parent scope: The space of the closure's block, module, or parent function. In other words, the parent scope is the outer/enclosing environment.
    • Global scope: The space containing all the scopes/code.

So, now that we know what a closure is, we can discuss how browsers will process createProfile()'s invocation.

How Do Browsers Process a Closure's Lexical Scope's Invocation?

Below is a walk-through of how JavaScript will process the createProfile() function's invocation.

1. Allocate memory

{ // This is the createProfile() function's opening block
Enter fullscreen mode Exit fullscreen mode

The first thing JavaScript does when browsers invoke createProfile() is to allocate the memory it needs to process the function's invocation.

2. Parse the variable's declaration

const myName;
Enter fullscreen mode Exit fullscreen mode

JavaScript's second task is to analyze myName's declaration, following the hoisting principles.

3. Parse the function's declaration

function showName() {}
Enter fullscreen mode Exit fullscreen mode

The computer's third step is to analyze showName()'s declaration, following the hoisting principles.

4. Initialize the variable

myName = "Oluwatobi";
Enter fullscreen mode Exit fullscreen mode

The computer initializes myName with the "Oluwatobi" string value, following the hoisting principles.

5. Return the invoked function's output

return showName();
Enter fullscreen mode Exit fullscreen mode

The fifth thing JavaScript does is to return showName()'s output to the createProfile() function.

6. Return the variable's content

return myName;
Enter fullscreen mode Exit fullscreen mode

JavaScript returns the variable's value to the showName() function's invocation.

7. Empty the allocated memory

} // This is the createProfile() function's closing block
Enter fullscreen mode Exit fullscreen mode

The computer ends the createProfile() function's execution and empties the allocated memory. Therefore, all the values JavaScript stored in createProfile()'s allocated memory will vanish forever!

So, what makes closures unique? Let's discuss this now.

What Makes Closures Unique?

No external scope can access a function's data by default, but a closure's scope can.

For instance, the snippet below returned an error because the global scope cannot access the createProfile() function's space.

// Define the createProfile function:
function createProfile() {
  const myName = "Oluwatobi";
}

// Call myName variable:
myName;

// The call above will return:
// Uncaught ReferenceError: myName is not defined
Enter fullscreen mode Exit fullscreen mode

Try Editing It

You can, however, use closures to access createProfile()'s scope from another environment.

Here's an example:

// Define the createProfile function:
function createProfile() {
  const myName = "Oluwatobi";
  function showName() {
    return myName;
  }
  return showName;
}

// Invoke the createProfile and store its output in a variable:
const firstName = createProfile();

// Check the content inside the firstName variable:
firstName;

// The call above will return:
// function showName() {
//   return myName;
// }

// Invoke the function stored in the firstName variable:
firstName();

// The invocation above will return:
// "Oluwatobi"
Enter fullscreen mode Exit fullscreen mode

Try Editing It

Note: createProfile() returned its inner function (the closure)—not the result of an invocation.

Question: Since the firstName variable contains only a function (showName()), how did the showName external function get access to createProfile()'s variable?

Answer: The reason the firstName variable's function gained access to createProfile()'s variable is because:

  1. The firstName variable's function is a closure.
  2. Closures retain reference to their lexical scope's data—even after JavaScript has closed their definition space's execution.

Let's discuss the reasons above in more detail.

How Do Closures Retain Access to Their Lexical Scope's Features?

Closures retain access to their lexical scope's features by storing referenced items in-memory.

For instance, reconsider our previous example:

// Define the createProfile function:
function createProfile() {
  const myName = "Oluwatobi";
  function showName() {
    return myName;
  }
  return showName;
}

// Invoke createProfile and store its output in a variable:
const firstName = createProfile();

// Check the content inside the firstName variable:
firstName;

// The call above will return:
// function showName() {
//   return myName;
// }

// Invoke the function stored in the firstName variable:
firstName();

// The invocation above will return:
// "Oluwatobi"
Enter fullscreen mode Exit fullscreen mode

Try Editing It

Here's is how the computer will analyze the snippet above:

1. Allocate memory

{ // This is the createProfile() function's opening block
Enter fullscreen mode Exit fullscreen mode

The first thing JavaScript does when browsers invoke createProfile() is to allocate the memory it needs to process the function's invocation.

2. Parse the variable's declaration

const myName;
Enter fullscreen mode Exit fullscreen mode

JavaScript's second task is to analyze myName's declaration, following the hoisting principles.

3. Parse the function's declaration

function showName() {}
Enter fullscreen mode Exit fullscreen mode

The computer's third step is to analyze showName()'s declaration, following the hoisting principles.

4. Initialize the variable

myName = "Oluwatobi";
Enter fullscreen mode Exit fullscreen mode

The computer initializes myName with the "Oluwatobi" string value, following the hoisting principles.

5. Return the function

return showName;
Enter fullscreen mode Exit fullscreen mode

The fifth thing JavaScript does is to return the showName function to createProfile().

Note the following:

  • The computer will store the returned showName function in the firstName variable since that's where the createProfile() invocator lives.
  • JavaScript will also store myName's value in the firstName variable's memory because showName referenced it.
  • The firstName variable's copy of showName and myName are new code instances the computer created during createProfile()'s execution.
  • Browsers will not run the "return myName" code at this stage because we did not invoke showName.

6. Empty the allocated memory

} // This is the createProfile() function's closing block
Enter fullscreen mode Exit fullscreen mode

The computer ends the createProfile() function's invocation and empties the allocated memory. Therefore, all the values JavaScript stored in createProfile()'s allocated memory will vanish forever!

TLDR

The main gist of the walk-through above is this:

  • The const firstName = createProfile() statement made the computer do the following:

    • Invoke createProfile().
    • Store the invocation's returned function in the firstName variable.
    • Store myName's current state (which showName() referenced) in the firstName variable's memory.

Therefore, here is what will happen whenever you invoke firstName's function:

1. Allocate memory

{ // This is the showName() function's opening block
Enter fullscreen mode Exit fullscreen mode

The first thing JavaScript does when browsers invoke showName() is to allocate the memory it needs to process the function's invocation.

2. Return the variable's content

return myName;
Enter fullscreen mode Exit fullscreen mode

The computer analyzes the return statement by doing the following:

  1. It checks if there's a myName variable definition locally within the showName() function's local scope. But there's none, so the computer moves up to check the next scope—the firstName variable's environment.
  2. JavaScript searches for myName in the firstName variable's memory. Fortunately, it found myName there! Therefore, the computer gets myName's content ("Oluwatobi") and returns it.

Note: The computer found myName in firstName's memory because JavaScript also stored all the data showName() referenced while storing showName inside firstName.

3. Empty the allocated memory

} // This is the showName() function's closing block
Enter fullscreen mode Exit fullscreen mode

The computer ends showName()'s execution and empties the allocated memory. Therefore, all the values JavaScript stored in showName()'s allocated memory will vanish forever!

Note:

  • The myName variable will persist even after the computer closes showName()'s execution. This is because JavaScript stored myName in firstName's memory—not inside showName()'s memory.
  • The computer will empty firstName's memory when it closes its scope's execution.

So, now that we know how closures retain access to their lexical scope's features, we can discuss a few more examples.

Example 1: Generate Prepublication Years

// Define the generateYear function:
function generateYear() {
  let publicationYear = 2023;
  function decreasePublicationYear() {
    return --publicationYear; // The -- operator subtracts one from the publicationYear variable.
  }
  return decreasePublicationYear;
}

// Invoke generateYear and store its output in a variable:
const prePublicationYear1 = generateYear();

// Invoke another instance of generateYear and store its output in a variable:
const prePublicationYear2 = generateYear();

// Invoke the function stored in the prePublicationYear1 variable:
prePublicationYear1();

// The invocation above will return: 2022

// Invoke the prePublicationYear1's function again:
prePublicationYear1();

// The invocation above will return: 2021

// Invoke the prePublicationYear1's function one more time:
prePublicationYear1();

// The invocation above will return: 2020

// Invoke the function stored inside the prePublicationYear2 variable:
prePublicationYear2();

// The invocation above will return: 2022
Enter fullscreen mode Exit fullscreen mode

Try Editing It

The snippet above consistently returns a value that is one (1) less than the previous year because:

  1. JavaScript also stored all the data decreasePublicationYear() referenced while storing decreasePublicationYear inside prePublicationYear.
  2. prePublicationYear's copy of the publicationYear variable remains accessible even after the computer closes decreasePublicationYear()'s execution.

Note:

  • prePublicationYear1 and prePublicationYear2 have different versions of generateYear()'s output because the computer creates new instances during each generateYear()'s execution.
  • The subtraction happens on prePublicationYear's copy of the publicationYear variable—not on the one inside the generateYear() function.
  • The --publicationYear code uses the decrement operator to subtract one from the publicationYear variable and return the value after the decrement.

Example 2: Generate the Multiple of a Constant

// Define the generateMultipleOfX function:
function generateMultipleOfX(x) {
  return function multiplyXByY(y) {
    return x * y;
  };
}

// Invoke generateMultipleOfX and store its output in a variable:
const multipleOfX = generateMultipleOfX(3);

// Invoke the multipleOfX variable's function:
multipleOfX(2);

// The invocation above will return: 6

// Invoke the multipleOfX variable's function again:
multipleOfX(10);

// The invocation above will return: 30

// Invoke the multipleOfX variable's function one more time:
multipleOfX(1);

// The invocation above will return: 3
Enter fullscreen mode Exit fullscreen mode

Try Editing It

The snippet above consistently multiplies multipleOfX's argument by 3 because:

  1. JavaScript also stored all the data multiplyXByY() referenced while storing multiplyXByY() inside the multipleOfX variable. (In the instance above, multiplyXByY referenced 3 (parameter x's value)).
  2. multipleOfX's copy of the value 3 remains accessible even after the computer closes multiplyXByY()'s execution.

Note:

  • The multiplication happened on multipleOfX's copy of generateMultipleOfX's argument.
  • Your closure can be an anonymous function. We named the ones above for ease of referencing.

Example 3: Create a Publication Year Calculator

// Define the publicationYearCalculator function:
const publicationYearCalculator = (function () {
  let publicationYear = 2023;
  function updatePublicationYear(value) {
    publicationYear += value;
  }
  return {
    addOne: function () {
      updatePublicationYear(1);
    },
    subtractOne: function () {
      updatePublicationYear(-1);
    },
    defaultYear: function () {
      return publicationYear;
    },
  };
})();

// Check the publicationYearCalculator variable's content:
publicationYearCalculator;

// The invocation above will return:
// {addOne: ƒ, subtractOne: ƒ, defaultYear: ƒ}

// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();

// The invocation above will return: 2023

// Invoke publicationYearCalculator's addOne() method:
publicationYearCalculator.addOne();

// The invocation above will return "undefined" because
// we programmed addOne() to invoke updatePublicationYear()
// only rather than return the result of the invocation.
// So, addOne() will increment publicationYear by one but
// will not return any value.

// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();

// The invocation above will return: 2024

// Invoke publicationYearCalculator's subtractOne() method thrice:
publicationYearCalculator.subtractOne();
publicationYearCalculator.subtractOne();
publicationYearCalculator.subtractOne();

// Similar to the addOne() invocation, the invocations above
// will return "undefined" because we programmed subtractOne()
// to invoke updatePublicationYear() only rather than return
// the invocation's result.

// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();

// The invocation above will return: 2021
Enter fullscreen mode Exit fullscreen mode

Try Editing It

The defaultYear() method remembers the publicationYear's updated state because:

  1. JavaScript also stored all the data updatePublicationYear() referenced while storing updatePublicationYear's invocator inside the publicationYearCalculator variable. (In the instance above, updatePublicationYear referenced publicationYear).
  2. publicationYearCalculator's copy of publicationYear remains accessible even after the computer closes updatePublicationYear()'s execution.
  3. The calculation happened on publicationYearCalculator's copy of publicationYear—not on the one inside the anonymous function.

Note the following:

  • The publicationYearCalculator variable's function is an Immediately Invoked Function Expression (IIFE).
  • Developers use the term "module pattern" to reference the neat organization of private and public features inside an object (as we've done in the publicationYearCalculator's IIFE above).
  • publicationYearCalculator's IIFE contains two private and three public items.
    • publicationYear and updatePublicationYear() are the private features.
    • addOne(), substractOne(), and defaultYear() are the public items because they are part of the returned object.
  • Although private items are inaccessible outside their lexical scope, you can access them through the public closures—because they have access to in-memory copies of their referenced features even after JavaScript has closed the IIFE's execution.
  • updatePublicationYear(), addOne(), substractOne(), and defaultYear() are all closures because they are inner functions that reference some of their lexical scope's features.

Tip: Exposing only the code you wish to make globally available is recommended. Private code restricts unwarranted access and accidental conflict with other interfaces/namespaces.

Overview

This article discussed what JavaScript closure is. We also used examples to see how it works.

Top comments (0)