DEV Community

Cover image for The secret lives of JavaScript closures
Habdul Hazeez
Habdul Hazeez

Posted on • Edited on

The secret lives of JavaScript closures

Cover photo by Joan Garnell on Unsplash.

Introduction

Closures date back to the 1960s, long before most modern programming languages and they've turned out to be quite an essential concept in solving some programming challenges. The name of the concept itself should give you an idea that to some extent it's about "closing" something up.

In this article we'll discuss closures in JavaScript, how they work, use case, advantages, and disadvantages. First, we'll begin with the general concept of closures in programming and its origin.

Table of Contents

General concept of closures

Wikipedia defines closures quite well and it goes thus:

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions.

This definition details what closure is all about which is: lexically scoped name binding and first-class functions.

Scope

In programming, scope is a region where a name binding is valid.

Name binding

This is the association of entities with identifiers.

First-class functions

These are functions that are passed as arguments to other functions, they can be assigned to a variable and they can be returned as the value of another function.

Origin of closures

As noted at the beginning of this article, closures dates back to the 1960s, once again Wikipedia got us covered:

The concept of closures was developed in the 1960s for the mechanical evaluation of expressions in the λ-calculus and was first fully implemented in 1970 as a language feature in the PAL programming language to support lexically scoped first-class functions.

Peter J. Landin defined the term closure in 1964 as having an environment part and a control part as used by his SECD machine for evaluating expressions. Joel Moses credits Landin with introducing the term closure to refer to a lambda expression whose open bindings (free variables) have been closed by (or bound in) the lexical environment, resulting in a closed expression, or closure. This usage was subsequently adopted by Sussman and Steele when they defined Scheme in 1975 a lexically scoped variant of LISP, and became widespread.

In the quote above, you'll note the following:

  • First-class functions
  • Lexical Environment
  • Free variables

Except for first-class functions (discussed earlier), we'll discuss the concept behind lexical environment and free variables later in the article.

In addition to the history from Wikipedia, Dmitry Soshnikov has this to say:

What this tweet entails is our next talking point.

Closures in JavaScript

At the onset of programming with JavaScript, the concept of closures might be a difficult thing to grasp, reality is, if you've written JavaScript code before you might have used (or created) a closure without realizing it.

Take the following code example:

let myName = "Habdul";

function getmyName() {
    let lastName = "Hazeez";
    console.log(myName + " " + lastName);
}

getmyName(); // Habdul Hazeez
Enter fullscreen mode Exit fullscreen mode

When the function getmyName() is called the output is Habdul Hazeez. Why is this?

You might not know it but this is a basic example of a closure. The function getmyName() was defined with an internal variable lastName and we appended its value with the value of the variable myName defined outside the function which led to the output Habdul Hazeez when the function is invoked.

Another question is: Why did function getmyName have access to myName variable? The answer is simple lexical environment.

Lexical Environment

From Stack Overflow:

Lexical Environment: it's the internal JavaScript engine construct that holds identifier-variable mapping (here identifier refers to the name of the variables/functions,and variable is the reference to actual object [including function type object] or primitive value).

A lexical environment holds a reference to a parent lexical environment.

And also from Stack Overflow:

Every function in JavaScript maintains a reference to its outer lexical environment. This reference is used to configure the execution context created when a function is invoked. This reference enables code inside the function to "see" variables declared outside the function, regardless of when and where the function is called.

Now we know why function getmyName had access to the myName variable. It's because function getmyName had a reference to its parent lexical environment and this reference enabled it to see the variables declared in this parent lexical environment or we can say this reference enabled the code inside the function to see variables declared outside the function.

In this context the parent lexical environment of function getmyName is the global object and the variable myName is known as a free variable.

Free variable

A free variable is a variable which is neither a parameter, nor a local variable of this function.

Let's take a look at another example:

let myName = "Habdul Hazeez";

function logmyName() {
    console.log(myName);
}

function getmyName(funArg) {
    let myName = "Jonathan Sanchez";
    funArg();
}

getmyName(logmyName); // ?
Enter fullscreen mode Exit fullscreen mode

What will be the output of getmyName() and why? Let's take a step backwards and analyze what is going on.

From the code snippet you'll notice the following:

  1. Free variable is in use (myName).
  2. Function logmyName is passed as an argument to function getmyName.

In JavaScript functions are first class citizens which means we can assign them to a variable, return them from a function, and pass them as an argument to another function.

Therefore, when we call the function getmyName as thus: getmyName(logmyName) which of the myName variable should it use? The one with the Habdul Hazeez or the one with Jonathan Sanchez?

This leads to a problem known as funarg problem.

Funarg problem

The funarg problem occurs when a programming language treats functions as first class functions which has to deal with free variables.

The funarg problem is further divided into two sub-type:

  1. downward funarg problem
  2. upward funarg problem

We just saw the downward funarg problem in action when a free variable was declared before a function that ends up using it.

Dmitry Soshnikov defines the downward funarg as:

an ambiguity at determining a correct environment of a binding: should it be an environment of the creation time, or environment of the call time?

Loosely meaning:

When a function wants to use a variable, should it use the one present at the time it was created or the variable at the time it was called (as demonstrated by our example)?.

In order to resolve this problem the function will use the variable declared at its time of creation not at the point of its invocation therefore, the function logmyName will use the variable myName declared where it was created which has the value Habdul Hazeez.

The upward funarg problem is illustrated in the following code snippet:

function details() {
    let myName = "Habdul Hazeez";

    // Closure, capturing environment of `details`.
    function getmyName() {
        return myName;
    }

    return getmyName;

}

let myDetails = details();

myDetails(); // Habdul Hazeez
Enter fullscreen mode Exit fullscreen mode

The function details consists of a local variable myName and a function getmyName. The function getmyName consists of a single statement which returns the myName variable. At this point it is said that we have captured the myName variable in a closure and we will be able to access it after the function completes its execution.

Later in the code we assigned the details function to myDetails variable and we invoke it as function. All this happened after the function declaration. This is the upward funarg problem where the capturing environment (getmyName) outlives the context which creates it (details).

Execution Context

In layman terms, execution context is the environment where your code is executed.

Technically, it's more than that and the term "execution context" is a source of confusion because it's not really about a "context" but about scope.

An execution context is created every time a function is invoked, it's composed of the activation object (the function's parameters and local variables), a reference to the scope chain, and the value of this.

// Global context

function one() {
    // "one" execution context

    function two() {

        // "two" execution context

    }

    function three() {

        // "three" execution context

    }

}
Enter fullscreen mode Exit fullscreen mode

Every execution context created is added to the top of the execution stack. The web browser will execute the current execution context that is found at the top of the execution stack. Once completed, it will be removed from the top of the stack and control will return to the execution context below.

Once removed, everything about the function that created the execution is destroyed, but we can preserve this state when we return an inner function which has access to the local variables, arguments, and inner function declarations of its outer function. This outer function is the parent lexical environment and the inner function is a closure.

function getMyName() {
    let myName = "Habdul Hazeez";

    // inner function
    return function() {
        return myName;
    }

}

let whatIsMyName = getMyName();
whatIsMyName(); // Habdul Hazeez.
Enter fullscreen mode Exit fullscreen mode

The variable myName is a free variable and for the inner function to search for it (before using it, in this case it simply returns it) a scope chain is used.

Scope chain

A scope chain is a list of objects that are searched for identifiers which appear in the code of the context. In general case, a scope chain is a list of all those parent variable objects, plus (in the front of scope chain) the function’s own variable/activation object (source).

From previous paragraphs we know about an activation object. But what is a variable object?

Once again, Dmitry to the rescue. He defined a variable object as thus:

A variable object is a container of data associated with the execution context. It’s a special object that stores variables and function declarations defined in the context.

Therefore, when the anonymous function could not find the variable myName as part of its local variables it used the scope chain to search for it and the variable was found in its parent variable object created for the function getMyName.

The scope chain is also used when we have deep nested function as shown in the example below.

function first() {
    second();
    function second() {
        third();
        function third() {
            fourth();
            function fourth() {
                // code here
            }
        }
    }   
}

first();
Enter fullscreen mode Exit fullscreen mode

The fourth function would have access to global variables and any variables defined within the three preceding functions.

Simply put, each time you attempt to access a variable within a function’s execution context, the look-up process will always begin with its own variable object. If the identifier is not found in the variable object, the search continues into the scope chain. It will climb up the scope chain examining the variable object of every execution context looking for a match to the variable name (source).

In ES5 the concepts of variable object, and activation object are combined into the lexical environments model discussed earlier.

Uses of JavaScript closures

As stated at the beginning of this article, closures do solve some programming challenges. It's impossible and impractical to cover them all, instead we'll discuss some situations that closures are really useful.

In no particular order they are:

  • Binding Event Handlers
  • Private Instance Variables
  • Data Encapsulation
  • Functional Programming
  • Modularization

Binding Event Handlers

Events occur as a result of user interaction with the application interface e.g. mouse clicks and key press.

JavaScript is used to handle events on a Web page and there are numerous ways to track events on a Web page.

Let's take a hypothetical example that we'll like to know which button was clicked on a Web page so that we can perform further actions after the click event.

<button>Click me</button>
<button>Click me1</button>
<button>Click me2</button>
Enter fullscreen mode Exit fullscreen mode

Our first approach can go as thus:

  1. Select all button on the Web page.
  2. Loop through the result then attach an event listener to each button.
var myElements = document.getElementsByTagName('button');

for (var i = 0; i < myElements.length; i++) {
    myElements[i].onclick = function() {
        alert( 'You clicked on: ' + i );
    };
}
Enter fullscreen mode Exit fullscreen mode

Note: We declared our variable using the var keyword just so we can see how closure was used to solve this kind of issue pre-ES6.

When each button is clicked, the result for i is 3 which is unexpected because 3 is the last assigned value to i. This problem can be solved using closure.

function getButton(n) {
    return function() {
        alert( 'You clicked on: ' + n );
    };
}

for (var i = 0; i < myElements.length; ++i) {
    myElements[i].onclick = getButton(i);
}
Enter fullscreen mode Exit fullscreen mode

The key to understanding the modified code is that every time getButton is called, a new closure is produced, and each of these closures has a different i.

Now, when the buttons are clicked everything works as expected.

NOTE: In ES6 the code be rewritten as:

let myElements = document.getElementsByTagName('button');

for (let i = 0; i < myElements.length; ++i) {
    myElements[i].onclick = function() {
        alert( 'You clicked on: ' + i );
    }
}

Private Instance Variables

Functions in JavaScript can have variables declared as formal parameters and these parameters can be returned using the return keyword.

When this function is used for creating objects with the new keyword, these variables are termed instance variables in this newly created object.

Let's take an example that you have the following code:

function Developer(first_name, speciality, age) {

   return `${first_name} ${speciality} ${age}`

}
Enter fullscreen mode Exit fullscreen mode

The variables can be modified easily leading to undesired results.

// Modify the variable
Developer.first_name = "John";
Enter fullscreen mode Exit fullscreen mode

Now, let's construct an object from this function.

let newDeveloper = new Developer('Ben', 'Webmaster', '100');
Enter fullscreen mode Exit fullscreen mode

When we check the details of newDeveloper we get an empty object due to the variable that we modified earlier.

newDeveloper;
// Object {  }
Enter fullscreen mode Exit fullscreen mode

When this object is expanded in the browser developer tools we get the following:

{}
<prototype>: {…}
    constructor: Developer(first_name, speciality, age)
        arguments: null
        caller: null
        first_name: "John"
        length: 3
        name: "Developer"
    prototype: {…}
    <prototype>: function ()
    <prototype>: Object { … }
Enter fullscreen mode Exit fullscreen mode

It's evident we've modified the variable first_name.

What if we can prevent this happening? That's when we can use private instance variables. In reality, JavaScript has no concept of "private variables" but we can simulate it with the use of closures.

Still using our example, we'll modify it by adding a method that will have access to the function variables, and it will prevent modification from external actors.

function Developer(first_name, speciality, age) {
    return {
        devDetails() {
            return `${first_name} ${speciality} ${age}`
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's repeat the steps we performed earlier.

// Try and modify the first_name variable
Developer.first_name = "Habdul";
Enter fullscreen mode Exit fullscreen mode

Construct an object from the function:

let john = new Developer('John', 'System engineer', '34');
Enter fullscreen mode Exit fullscreen mode

Moving forward, we check the developer details by invoking the devDetails function and it will work as expected.

john.devDetails(); // "John System engineer 34
Enter fullscreen mode Exit fullscreen mode

This was not the case when the variables were free for modification causing issues along the way. You can type the variable name john in the browser developer tools and expanding the output. It should be different compared to when we modified the first_name variable.

Data Encapsulation

Encapsulation is the process of exposing what another part of a program can access when they are divided into smaller components whereby some components are public and others are private. This includes the following:

  • Variable names
  • Functions
  • Methods (functions in an object)

In JavaScript, encapsulation can be achieved using closures as seen in the following example from CSS-Tricks.

const CarModule = () => {
    let milesDriven = 0;
    let speed = 0;

    const accelerate = (amount) => {
        speed += amount;
        milesDriven += speed;
    }

    const getMilesDriven = () => milesDriven;

    // Using the "return" keyword, you can control what gets
    // exposed and what gets hidden. In this case, we expose
    // only the accelerate() and getMilesDriven() function.
    return {
        accelerate,
        getMilesDriven
    }
};
Enter fullscreen mode Exit fullscreen mode

Functional Programming

Functional programming is mostly about functions. And we already know that closures can be a normal function in JavaScript or an inner function meaning we've done a bit of "functional programming" in this article. Well, let's talk about the relationship between FP (functional programming) and closures.

In the example illustrated below we'll like to add two numbers using currying.

Currying has its roots in mathematics and computer science and it's the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument (source).

function add(a) {

    // The anonymous function closes over the
    // variables a and b
    return function(b) {
        return a + b;
    }

}

add(1)(2); //3
Enter fullscreen mode Exit fullscreen mode

The function add takes just a single argument but it returns another function (the closure) which takes another argument and in-turn returns the result of the addition.

Modularization

Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality (source).

This involves grouping some lines of code into a unit that can be included in the program. Can you guess the name of this type of unit? Functions. These functions can in-turn contain another function. You see where I am going? Bottom line; closures. Which we already learned it can be a single function or a function inside another function.

An example of writing modular JavaScript code is the following example from Stack Overflow.

let namespace = {};

// All implementation details are in an Immediately
// Invoked Function Expression (IIFE)
(function foo(n) {

    let numbers = []

    function format(n) {
        return Math.trunc(n)
    }

    // Capture the numbers variable in a closure
    function tick() {
        numbers.push(Math.random() * 100)
    }

    // Capture the format function in a closure
    function toString() {
        return numbers.map(format)
    }

    // Expose the tick and toString function
    // to the public
    n.counter = {
        tick,
        toString
    }

}(namespace))

// Assign the public methods to a variable
const counter = namespace.counter;

/**
 * Invoke the tick function twice which automatically pushes
 * a random number into the private variable
 * numbers.
 */
counter.tick();
counter.tick();

// Invoke the toString method
console.log(counter.toString()); // Example output: Array [ 42, 46 ]
Enter fullscreen mode Exit fullscreen mode

It's evident that the function tick and toString capture the state of the variable (numbers) and function (format).

Advantages of closures

The uses we've discussed so far.

Disadvantages of closures

Closures are useful, but they also have their disadvantages. They are:

  • As long as closures are active, this memory cannot be garbage collected.
  • Creating functions inside other functions lead to duplication in memory, potentially slowing down the application.

Conclusion

In this article we've talked about JavaScript closures, but we did not cover some of its deep technical details therefore, I'll encourage you to have a look at additional literature's in the references.

References

Updated September 11, 2020: Grammar fix.

Top comments (0)