Scope, Functions and Closures

It's not my intention to repeat the excellent explanations of others on the subjects of scope, functions and closures (some linked below). Instead this is meant to be a quick code focused reference for the occasions in which I find myself able to share what I've learned with others.

Scope

Scope is one of those subjects that we learn early on, think we understand and will therefore believe we can bypass. I believe understanding scope is important to understanding closures so I'll review in brief here.

The base scope in Javascript is the global scope which in the browser is called window. Anything defined in the global scope is acccessible anywhere else in our programs. We know this by habit even if we haven't consciously considered it. When we define the add function in a script tag we can call (or invoke) it anywhere else in our program. Open the console and try it out.

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

add(1,2); // returns 3

Javascript has function scope. In other words when we wrote the add function we created a new scope. add's scope is anything we defined between () and {} when we wrote the add function; in this case the parameters a and b.

add can access its own function scope and it can access it's parent's scope; in this case the global scope. Again we know this by habit.

var a = 1;

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

add(2); // returns 3

function tenMinus(1) {
    return 10 - add(a, 1);
}

tenMinus(2) // 7

The examples are very contrived but it comes as no surprise that add can access a or that tenMinus can access add. A function can access it's own scope and the scopes of its parent context or contexts. Conversely a parent scope can't access the scope of a child or function. There is no way to access the parameter b of the add function from the global scope.

In JavaScript we can write functions anywhere including inside of other functions.

var a = 1;
function add (b) {
    function times () {
        return a * b;
    }

    return a + times();
}

Additional nested functions create additional nested scopes and the rules continue to apply. The global scope can't access add's scope and add can't access times' scope. But times can access both add's scope and the global scope while add can as before access the global scope.

At this point you may be wondering why I've left let and const out. I've done so because they work with block scope1 which is pretty awesome but we're going to stay focused on function scope for this discussion

A last note about function scope before moving on. When we invoke add(1,2) the run-time environment creates a new function scope for this invocation of add. 1 and 2 are inserted as the values of a and b. The add function then runs to completion. After returning 3 the function scope is destroyed. This rule can be broken as we'll see later.

Functions

You may have heard that functions in JavaScript are first class objects or perhaps you've heard as Javascript has first class functions2. Either way is means the same thing. In JavaScript functions are treated like any other variable type. This is worth clarifying. In Javascript a function can be assigned to a variable, passed as an argument to a function or returned as the result of a function just like strings, arrays and etc.

This is again something we probably "know" beause we've typed it in hundreds of times but the implications are far reaching.

// 1
function add (a, b) {
    return a + b;
}
    
// 2
const add = (a, b) => a + b;

// OR 3
const add = function (a, b) {
    return a + b;
}

Above we see our initial add function (1) is declared using the function keyword. This is called a "function declaration"3. In examples 2 and 3 we use a "function expression"4 to declare the add function. There are some subtle differences between function declarations and function expressions but in this case the key takeaway is that add is NOT a function.

const o = { key: 1 }

Consider for a moment the creation of an object with a single key value pair. Is o an object? At first glance you may be inclined to say yes but o is not an object. o is a name that references someplace memory where an object lives. Functions are no different. add is not a function. add is a name that references a place in memory where a function is stored.

If add and o are both just names referring to memory what happens when we do the following?

const p = o;
const a = add;

We probably know that p is now a references to the same data stored in memory as o. You may know this as "objects in javascript are passed by reference." But what's the case with a? Is it also a reference to the same function as the add keyword. If so can we invoke it?

a(3,4) // returns 7

a is just another name for add. a can be invoked just like add. Go ahead and try it in the console.

Just as a function can be assigned to a variable it can also be passed as an argument to a function and can again be invoked.

In this example we pass add as an argument to the math function which in turn invokes add using the name callback. This is again something we may know by habit. Every time we use a foreach or map (and many other built-in methods) we pass a function as an argument to another function.

Let's now turn to returning a function from a function.

The addOrSubtract function is a function that returns either the add function or the subtract function based on a string, "+" or "-" in this case5. When we invoke addOrSubtract with "-" what is the value of m? m is now a reference to subtract. When we invoke m with 3 and 2 the subtract function is called and 1 is returned.

This is pretty cool and is the first important step in learning how you can use functions to create functions in Javascript but it begs a further example.

function incrementer (startNum) {
    let i = startNum;

    function increment () {
        return ++i;
    }

    function decrement () {
        return --i;
    }

    return {
        increment,
        decrement
    };
}

const myInc = incrementer(2);
myInc.increment(); // ?
myInc.decrement(); // ?

incrementer is a function that instatiates a variable index which holds a number. It returns an object containing both increment and decrement functions that change the value of index and return it.

"But wait," you say. "Way back up there in Scope you told me functions run to completion and afterwards their function scope is destroyed." I did say that. I also said this rule can be broken.

Closures

Let's pause for a moment to consider the scopes both implicit and created when we wrote the incrementer function. First there is the global scope known as window in the browser. The incrementer function creates its own scope and within it defines the i variable, increment and decrement functions. The increment and decrement functions in turn have their own scopes.

As we learned earlier the increment and decrement functions can access their own scope and all of their ancestor scopes, incrementer and global in this case. incrementer in turn can access its ancestor scope, the global scope. When we invoke incrementer the environment creates a new function scope for incremeter, the value 2 is assigned to i the increment and decrement functions are created and then returned. But this time the function scope created by invoking incrementer is not destroyed.

If we return a reference to the function scope or "lexical environment" of a function that scope is preserved and can continure to be referenced during the lifecycle of our program. That reference is called a "closure". It is "...the combination of a function and the lexical environment within which that function was declared."6

Believe it or not this is probably something you've seen and perhaps done many times in the past without knowing there's a name for it. If you've worked in what now seems like the old-timey web and wrote a browser based program then you've most certainly worked with the revealing module pattern.

(function (win) {
    // ...plugin code

    return function init () {}
}, (window))

This pattern keeps the global scope clean and free of conflicts with other browser programs while exposing a public interface. This is a closure.

On the other hand you may be more familiar with more current coding examples like this definition of add.

const add = x => y => x + y;

If we read this definition of add carefully then we'll see that add is a function that takes an argument named x and returns a new function. The new function takes an argument named y which returns the sum of x and y.

const add2 = add(2);
add2(5) // 7

Here we invoke add with the value 2. add returns a new function that wants an argument y and that function is assigned to the variable add2. When add2 is in turn invoked with 5 the final part of the add function is called and 2 + 5 = 7.

Chaining, sort of

The syntax of our last definition of add necessarily creates and odd sort of syntax. If for instance we wanted to add 2 and 3 we'd either have to write a lot of code or use more parenthesis.

const add = x => y => x + y;

const add2 = add(2);
add2(3); // a lot of work for 5

// or
add(2)(3) // weird!

However if you're familiar with dot chaining this isn't so odd.

[2,1,3].map((n) => add(n, 2)).sort();

In this code block we add 2 to each value in an array and then sort it. We may take it for granted that .map and .sort will work but they work because each method invocation returns an Array and Arrays have both the map and sort methods. Dot chaining may in turn break if we call a method that returns an unexpected type.

[2,1,3].join().sort();
// Uncaught TypeError: [2, 1, 3].join().sort is not a function

This code fails because join returns a String and String does not have a sort method. Our code would not break however if we called a valid String method.

[2,1,3].join().toLocaleUpperCase(); // please enjoy a capitalized string

We could call our chained code like this.

const arr = [2,1,3];
const summedArr = arr.map((n) => add(n, 2));
const sorted = summedArr.sort()

We don't write our code that way because it's long and the interrim steps of assigning values to variables is unnecessary. Our last add code from above (repeated here) is no different.

const add = x => y => x + y;

const add2 = add(2);
add2(3); // a lot of work for 5

// or
add(2)(3) // weird!

We can go through the extra steps of assigning interrim states to variables but it's not necessary. If we know a function will return a function we can call it immediately. And like trying to chain sort to a String if we get it wrong we'll get a type error.

const addOne = x => x + 1
addOne(2)(3) // Uncaught TypeError: addOne(...) is not a function

The moral of this story is, don't fear parenthesis.

And a Whole Lot More…

Closure or the ability of a function to retain a reference to the lexical environment in which it was declared is in my opinion the key to understanding the functional nature of Javascript. By "functional nature" I don't mean its ability to work. I mean the "Functional Programming" paradigm.

That is perhaps a longer conversation for another time.