Page 3: Closure

The nested scoping rules become more interesting when we start passing around those functions we create. We’ll still use boring toy programs for this page. We’ll get to a real application soon.

Closure 1

Here’s a simple function inside of a function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function f() {
    let a = 0;
    const g = function() {
        a = a+1;
        console.log(a);
    }
    return(g);
}
let b = f();
b();
b();

Some things to note here:

  1. the function f returns the function object that was stored in g (line 7)
  2. the function g is defined inside of f (lines 3-6)
  3. the variable a used inside of g is the one defined on line 2
  4. the variable b is assigned the result of f (which was the function formerly known as g)
  5. the function stored in b is called twice (on lines 10 and 11)

Hopefully, it makes sense that this program will print 1 then 2. When we call the function stored in b, we are executing the function defined on lines 3-6 (which access the a variable, defined on line 2). The first time we call it, it increments a (which is 0, so it will get the value 1) and prints it (so it prints 1). The second time we call it, it increments a (which is 1, so it will get the value 2) and prints it.

The thing to observe: the function object not only contains the code (lines 3-6) it also “encloses” the variable defined on line 2.

Two Closures

OK, Same function, but we will use it twice

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function f() {
    let a = 0;
    const g = function() {
        a = a+1;
        console.log(a);
    }
    return(g);
}
let b = f();
let c = f();
b();
b();
c();

If you understood the previous example, you should see that the two calls to b (lines 11 and 12) will print 1 and 2 respecitvely. Figuring out what line 13 prints is a little trickier, and requires us to really understand what the closure does.

On line 9, we call the function f which uses the function keyword to create a “function object” that encloses the a variable defined on line 2.

On line 10, we call the function f again, which repeats that process. This second time we call f, we get a “new” a variable (every time we call a function, we get a new “copy” of its local variables). When we call the function keyword, that creates another function object, which encloses that new a variable.

So, by calling the f function twice, we’ve created two different function objects (you can think of it as if we ran the compile to make a new function object twice, although the compiler really doesn’t run twice). Each of those function objects encloses a different version of the a variable.

So, line 13 prints 1. It is using the second function object. The a inside of the first function object was incremented twice already (on lines 11 and 12). But the a inside of the second function object (stored in the c variable) is a different a - it started from zero.

That’s closure.

Closures with Arguments

Closure applies to function arguments as well - they are just like any other variables.

Here’s a toy example:

1
2
3
4
5
6
7
8
function adder(x) {
    return function(y) { return x+y; };
}
let a1 = adder(1);
let a3 = adder(3);

console.log(a1(5));     // prints 6
console.log(a3(7));     // prints 10

The adder function is a function that returns a function that adds an amount to its argument. Adder takes one argument x. It returns a function that takes one argument y. That function adds x and y together.

On line 4, we set a1 to be the function that adds its argument y to the value of x that is enclosed in it. In this case, x was 1 (the value of the parameter to adder).

On line 5, we call adder again, to have it produce another function object. This time enclosing a different copy of x that happens to be set to 3.

In this case, x isn’t going to change (each different version of x is different, but those versions don’t change their values). But it is important to remember that closures enclose the variable, not the value.

Beyond toy examples

Hopefully, these simple examples get the concepts of a closure across. We create function objects than “enclose” the variables around them.

Closure can be a convenient way to write programs where we need to create functions (for example handler functions) that have private data. There are other ways to program these things, but once you understand closures, they are a useful tool.

On the next page we’ll look at a more realistic example.

Next: Closure Example