{ Closures. }

Objectives:

By the end of this chapter, you should be able to:

  • Explain what a closure is
  • Use closure to create "private" variables
  • Create a module using the module pattern
  • Understand what the keyword arguments represents and how it can be used

Closure

One of the most difficult concepts to understand when first learning JavaScript is closure. Let's take a stab at a definition:

Closure is when a function is able to access variables from an outer function that has already returned.

Thanks to JavaScript's inner workings, a function is able to remember variables defined in functions even if that function has returned. Let's see what we mean by that with an example.

function outer(a){
    return function inner(b){
        return a + b;
    }
}

outer(5) // this returns the inner function

// this calls the inner function right away
outer(5)(2) // 7

// we can store the inner function in a variable
let laterAdd = outer(10)

// we can now call that inner function
laterAdd(15) // 25

// but how was the inner function able to remember the parameter "a" defined in the outer function which already returned? The answer is through closure.

Closures in the wild

There are many use cases for closures. One of the more common ones is to create a "private variable," or a variable that can not be accessed directly (and overwritten). Here's an example that uses closure to create a "private" variable.

function defineAge(){
    let age = 28;
    return function growUp(){
        return ++age;
    }
}

let ageOnce = defineAge();
ageOnce(); // 29
ageOnce(); // 30

So who can access our age variable? Only the defineAge function, which has returned, and the growUp function, which, through the use of closure, has access to the age variable. Amazingly, this is true even though the outer function defineAge has already returned. Our age variable is now protected and no one can gain access to it! In cases like this, we say that growUp (the inner function) has closure over the scope of defineAge.

Let's look at another example:

function createInstructors(){
    let instructors = ["Elie", "Matt", "Tim"];
    return {
        showInstructors: function displayAllInstructors(){
            return instructors;
        },
        addInstructor: function addNewInstructor(instructor){
            instructors.push(instructor)
            return instructors;
        }
    }
}

let firstClass = createInstructors();
firstClass.addInstructor("Jennifer");
firstClass.showInstructors(); // ["Elie", "Matt", "Tim", "Jennifer"]

let secondClass = createInstructors();
secondClass.addInstructor("Ashley"); // ["Elie", "Matt", "Tim", "Ashley"]

// on one line

let instructors = createInstructors().showInstructors(); ["Elie", "Matt", "Tim"]

Now that one line we just wrote was pretty neat! let instructors = createInstructors().showInstructors();, but could we do better? What if we do not want to call createInstructors every time, so that we could write something like createInstructors.showInstructors()? To do that, we can use IIFEs!

let instructorModule = (function createInstructors(){
    let instructors = ["Elie", "Matt", "Tim"];
    return {
        showInstructors: function displayAllInstructors(){
            return instructors;
        },
        addInstructor: function addNewInstructor(instructor){
            instructors.push(instructor)
            return instructors;
        }
    }
})();

What we have just created is a small module, which is a piece of code that is encapsulated and can be reused quite easily. The pattern we just used to write our code is famously called the module pattern! It's a great way to wrap everything in an IIFE that contains private data that can not be accessed globally. We can even refactor this more so that our logic is not in the return statement.

let instructorModuleRefactored = (function createInstructors(){
    let instructors = ["Elie", "Matt", "Tim"];
    function displayAllInstructors(){
        return instructors;
    }
    function addNewInstructor(instructor){
        instructors.push(instructor);
        return instructors;
    }
    return {
        showInstructors: displayAllInstructors,
        addInstructor: addNewInstructor
    }
})();

More on closures

If you would like to see some more examples on closures take a look at the video below. The slides for this video are here

The arguments array (well not exactly...)

Every single time that a function is called, we get access to a special keyword called arguments which looks like an array (it is not EXACTLY an array, but we will cover the reason why a bit later) and can be accessed using [] notation. Here is an example:

function logAll(){
    console.log(arguments);
}

logAll(2,2); // [2,2]
logAll(10,5,4); // [10,5,4]
logAll(1); // [1]

function displayFirstArgument(){
    return arguments[0];
}

displayFirstArgument(10,20); // [10]
displayFirstArgument(true); // [true]
displayFirstArgument(); // []

So what do we mean when we say not "exactly" an array? Let's see what happens if we try to use the push method on the arguments keyword

function tryPushOnArguments(){
    arguments.push("another one");
    return arguments;
}

tryPushOnArguments(); // Uncaught TypeError: arguments.push is not a function

The reason this is happening is because even though the arguments keyword looks like an array and even has a length property, it is actually not an array. It is a special kind of object.

Manipulating this arguments array-like object can be useful, for instance, when you don't know how many arguments someone will pass in to your function:

function add() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i]; // this is shorthand for total = total + arguments[i]
    }
    return total;
}

add(1,2,3); // 6
add(1,-2,3,-4); // -2
add(); // 0

Exercises

Answer the following questions:

  • What is a closure?
  • List two reasons why closures are useful
  • What is a module?
  • What is the arguments array-like object?
  • Why do we call arguments an array-like-object?

  • Write a function called createCounter. This function should contain a variable count that is initialized to 0. This function should return another function that when invoked, increments the counter by 1 and returns the count variable. You should be able to create multiple counters with the createCounter function and they should all have their own private variable called count.

let firstCounter = createCounter();

firstCounter(); // 1
firstCounter(); // 2
firstCounter(); // 3
firstCounter(); // 4

let secondCounter = createCounter();

secondCounter(); // 1
secondCounter(); // 2
secondCounter(); // 3

firstCounter(); // 5
firstCounter(); // 6

secondCounter(); // 4

When you're ready, move on to Higher Order Functions, Timers, and Closures Exercises

Continue

Creative Commons License