1. javascript
  2. /references
  3. /closures

JavaScript Closures Overview

Getting Started

As a versatile feature of JavaScript, closures enable us to create functions that have access to variables outside of their own scope, allowing us to build more modular and maintainable code.

Pure Functions

Before we dive into closures, let's take a quick look at pure functions. So, a pure function only depends on its arguments and internal data; it's fully self-contained and doesn't reference any variables outside of its scope.

function add(a, b) {
  return a + b;
}

Our example function depends only on its arguments a and b and doesn't reference any variables outside of its own scope.

Closures

When we call a function that references variables from the surrounding environment, the JavaScript interpreter creates a closure to store those variables in a place in memory where they can be accessed later.

Simply put, closures are functions that reference variables outside of their own scope.

Creating a Closure

We can create a closure by defining an outer function that contains the state, and then an inner function that operates on it. The data contained in the outer function will not leak out to the surrounding environment, but the inner function has access to data defined in the outer function scope. Like so:

function outer() {
  const data = 'Hello, World!';
  return function inner() {
    console.log(data);
  };
}

const innerFn = outer();
innerFn(); // logs 'Hello, World!'

Let's dive into more detail and explain the syntax.

Here, the outer function defines a variable data, and then returns the inner function inner that logs the value of our data. Upon calling outer, it returns inner, and we can then call inner to log the value of the data which in this case is the string 'Hello, World!'.

Memory and Performance

Closures require more memory and processing power than pure functions since they need to store data in memory indefinitely. However, they provide many practical benefits, such as data encapsulation to prevent data leakage or exposure where it's not needed. Naturally, having proper judgment, and understanding these implications comes with a lot of exposure to the language.

Callbacks and Closures

We can use closures to create a "function factory" that takes an argument and returns a brand new function, which can then be passed along to other ones that expect a callback.

To demonstrate how closures encapsulate the state and create reusable functions, we'll use the following example:

function alertExample(message) {
    return () => {
        alert(`${message}`)
    }
}

const alertAdvice = alertExample('Be patient.');
const alertWarning = alertExample('Be careful!');

alertAdvice(); // displays 'Be patient.'
alertWarning(); // displays 'Be careful!'

By creating two instances of alertExample with different messages, we receive two unique functions that we can reuse throughout our application. These functions use the same inner function but contain contrasting parameters.

The Infamous Closure Interview Question

What we see below, is a common JavaScript trick question that might prove to be challenging if you don't understand the closure caveat in the example.

The Question

What would be the log output of this code?

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

The Answer

Feels unintuitive, but the output of the code is 3 3 3.

To understand why this happens, we need to understand the difference between var and let in terms of scope. When we use var in a for loop, that variable actually gets hoisted up into the parent scope, which in this case would be the global one.

So, when var is used to declare the variable,i is globally scoped. Also, when we reference i inside the setTimeout callback, it's referencing the same variable from the global scope, which has been incremented to 3 by the end of the loop.

In contrast, if we use let, the variable i will be scoped to the loop, meaning that each iteration will have its own variable i.

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 100);
}

With this approach, the output is 0 1 2, which is what we would expect.

Additional Resources

Variables in JavaScript

An Overview of Callback Functions

The Basics of JavaScript Loops

JavaScript Scope