JavaScript Closures: A Beginner's Guide

javascript
A woman looking at the text 'Javasript Closures' and getting scared

What is a Closure?

JavaScript closures are the "magic" that lets functions remember and access their enclosing (outer) scope variables, creating a dynamic link - even after the enclosing (outer) function has finished executing. This magical bond gives the functions power to remember and manipulate data.

That means that even though the outer function is no longer around, the inner function can still access and modify the variables that were defined in the outer function.

Here is an example:

function makeCounter() {
      // Create a counter variable inside the outer function
      let counter = 0;
    
      // Create an inner function that increments the counter and returns the new value
      function increment() {
        counter++;
        return counter;
      }
    
      // Return the inner function
      return increment;
    }
    
    // Create a counter object
    const counter = makeCounter();
    
    // Call the counter function twice to increment the counter
    console.log(counter()); // Output: 1
    console.log(counter()); // Output: 2
    

The makeCounter() function creates an inner function increment() that increments the counter variable inside the outer function. Even though the makeCounter() function has finished executing, the increment() function can still access the counter variable because it was captured during the creation of the closure.

 

Understanding Scope and Lexical Scoping

Before going forward, let’s see what exactly “scope” means.

  • Scope refers to the context in which variables and functions are declared and accessed
  • Scope determines the visibility and accessibility of these variables and functions throughout your code

There are two main types of scope:

  1. Local scope: Variables declared inside a function or block have local scope. They are only accessible within that specific function or block.
  2. Global scope: Variables declared outside any function or block have global scope. They can be accessed from anywhere in the code, including within functions.

Lexical Scoping

Note: Okay. This is about to get as confusing as a cat trying to walk on a keyboard, but I’ll do my best to translate!

Lexical scope ensures that functions can be reach out to variables in their containing scopes. That allows them to use variables from their parent functions (or the global scope).

It is a way of determining the scope of a variable based on where it is declared. JavaScript uses lexical scoping to ensure that variables are not accessible from outside their declared scope. (This helps to prevent errors and make code more predictable)

Where a variable is declared in the code affects where it can be accessed. You decide where to place the variables. And that decision dictates where they will be accessible throughout your code.

  • The scope of a variable is determined by its location in the source code.
  • The visibility and accessibility of a variable are defined by where it is declared and not by where it is executed.
function outerFunction() {
      let x = 10; // x is a local variable
    
      function innerFunction() {
        console.log(x); // This is allowed because x is in the outerFunction's scope
    
      // The lexical scope of innerFunction is the outerFunction function. 
        // This means that innerFunction can access variables that are declared within the outerFunction function...
        // ...or in its parent scope, which is the global scope.
      }
    
      innerFunction();
    }
    
    outerFunction();
    

Here the innerFunction() can access the x variable because it is declared in the outer function's scope. This is even though the innerFunction() is nested inside the outerFunction().

 

How Closures Capture Variables

Closures capture variables from their enclosing scope by "remembering" the environment in which they were created (through the mechanism of lexical scope).

When a function is defined within another function, the inner function forms a closure and retains access to the variables of its outer (enclosing) function, even after the outer function has finished execution.

This ability to retain access to variables is what makes closures so powerful.

 

Applications of Closures

Closures find their applications in various scenarios, including:

  • Private Variables: Closures can be used to create private variables, which are only accessible within the closure itself. This allows for better encapsulation of data and code.
  • Memoization: Closures can be used to cache function results, improving performance when repeatedly calling the same function with the same input.
  • Callbacks and Event Handling: Closures can capture data and functions within callback functions, enabling flexible and reusable event handling mechanisms.