What are JavaScript Closures?

Learn about JavaScript Closures.

“Everything must be made as simple as possible. But not simpler.”
― Albert Einstein

1. Introduction

JavaScript supports closures which is a concept that is somewhat hard to understand, so let us step you through some examples to help.

2. A Simple Function

You can define a function in JavaScript quite simple by using the function keyword. Here is a sample definition.

function joe() {
    console.log("hello world");
}

Once this function is defined, it can be invoked (or run) as follows:

joe()
// prints "hello world"

3. A Function Returning a Function

Let us now define a function returning a function.

function joe() {
    return function() { return "hello world" }
}

On invoking the function, it returns a function which can be stored in a variable.

var f = joe()
// returns f () { return 'hello world'; }

And of course, you can invoke the function stored in the variable as follows:

f()
// returns "hello world"

4. Referencing a Local Variable

Let us now define a function that returns a function which refers to a local variable in the defining context.

function joe() {
    var x = 'joe';
    return function() { return 'hello ' + x }
}

As before, invoking the function returns a function. Invoking the returned function returns a string with the value of the local variable x included in it.

var f = joe();
// returns f () { return 'hello ' + x }
f()
// returns "hello joe"

Here, the returned function called a closure — it includes values of local variables from the defining location that are referenced within the function definition. In other words, the local variables where the function is defined are packed to go.

(Of course, not all local variables are packed to go, only the ones referred to in the function definition. This, however, is an implementation detail of the language in question, JavaScript.)

5. Modifying a Packed Local Variable

Is it just the value of the local variable that is packed-to-go? Or a reference to the variable in question? In other words, what happens when the variable is modified after it is packed-to-go? Let us find out.

Here the returned function is packed-to-go, and the value is updated before returning it.

function joe() {
    var x = 'joe';
    var f = function() { return 'hello ' + x; }
    x = 'jack';
    return f;
}

Let us invoke the returned function and see what happens.

var f = joe();
f();
// returns "hello jack"

The updated value is returned. In other words, the closure stores a reference to the value packed, and any updates to the packed variable is reflected in the original definition.

Here is a slightly complex example where the variable is packed into two functions, each of which performs a different update.

function joe() {
    var x = 0;
    var f = function() { x++; return 'f - value is ' + x; }
    var g = function() { x += 2; return 'g - value is ' + x; }
    return [f, g]
}

Both updates are reflected in the value of the variable as expected. No suprises there.

var a = joe()
a[0]()
// returns "f - value is 1"

a[0]()
// returns "f - value is 2"

a[1]()
// returns "g - value is 4"

a[0]()
// returns "f - value is 5"

a[1]()
// returns "g - value is 7"

In other words, what we have here is a way to update the value returned from a function by remote control.

6. Beware of Defining a Closure in a Loop

The above example illustrates why defining a closure in a loop might give unexpected results. When you define a function inside a loop, and pack a loop variable to go, remember that the variable is packed by reference. Any updates made to the variable is reflected in all the functions.

Let us illustrate with an example. The following assembles an array of functions, each of which pack the loop variable i and the calling function argument, a. It should be obvious (from the example in the above section) that when each function in the array is invoked, the value of i is always 1 + a.length (since that is its value at the end of the loop).

function joe(a) {
    var r = []
    for(var i = 0 ; i < a.length ; i++) {
        r.push(function() { return "item " + i + " => " + a[i] });
    }
    return r
}

And so it is:

var a = joe([1, 2, 4, 8])
for(var i = 0 ; i < a.length ; i++) {
    console.log('fn[' + i + '] => ' + a[i]());
}
// prints:
fn[0] => item 4 => undefined
fn[1] => item 4 => undefined
fn[2] => item 4 => undefined
fn[3] => item 4 => undefined

So, yeah, we have a problem.

And how do we fix it? Since the only way to create a scoped variable in JavaScript prior to ES6 is to declare it in a function, we need to copy the value of i into a local variable (called k here) in a new scope.

In the following an anonymous function is defined for a new scope for each iteration, and immediately invoked.

function joe(a) {
    var r = []
    for(var i = 0 ; i < a.length ; i++) {
        (function() { var k = i; r.push(function() { return "item " + k + " => " + a[k] })})();
    }
    return r
}

Now it all works correctly.

var a = joe([1, 2, 4, 8])
for(var i = 0 ; i < a.length ; i++) {
    console.log('fn[' + i + '] => ' + a[i]());
}
// prints:
fn[0] => item 0 => 1
fn[1] => item 1 => 2
fn[2] => item 2 => 4
fn[3] => item 3 => 8

7. Using an Undeclared Variable

The function returned by another function may reference an undefined variable. When it is invoked, the value for this variable is picked up from the runtime context as shown below. If a value is not defined in the runtime context, a ReferenceError is thrown.

function joe() {
    var f = function() { return 'hello ' + x }
    return f
}

joe()()
// throws "Uncaught ReferenceError: x is not defined"

Defining a value in the current context causes the function to pick up that value.

var x = 'jack';
joe()()
// returns "hello jack"

You can also define the variable subsequent to the declaration of the function. That value is then picked up at invocation.

function joe() {
    var f = function() { return 'hello ' + x };
    var x = 'joe';
    return f
}

joe()()
// returns "hello joe"

Summary

A JavaScript closure is a function returned as a result of another function which includes the local environment where the function is defined. It stores the value of the referenced local variables as they were when the function exited. Subsequent invocations of the returned function can use these variables, and also modify them. Multiple closures created within a function all share the same context, so modifications are visible from all such closures.

Leave a Reply

Your email address will not be published. Required fields are marked *