Exploring Closures in JavaScript cont.

Image for post
Image for post

To recap my last blog, “Exploring Closures with JavaScript Functions”, to understand closures, it’s essential to remember that functions create an enclosed environment known as a namespace. Namespaces allow you to work with variables that have been defined within other namespaces or scopes without overwriting them. Now here is the critical part, when a function is defined, the function object that is created not only contains the code defined within its code block but also a reference to the namespace it was defined in, and the variable bindings within the namespace.

function outerFunc() {
const outerFuncScope = "outerFunc";
return () => console.log(outerFuncScope;
}

When the anonymous function is returned from the outerFunc() call, it maintains access to the outerFuncScope variable binding because it was defined within the outerFunc() namespace and so it keeps a reference to that namespace and all the variable bindings within it.

Again, utilizing closures allow for very some powerful programming. In fact, closures are a big component in functional programming which is essentially how the React UI library is built. Let’s take a look at some ideas that are powered by closures.

One idea that I find very interesting is that of using closures to maintain a private state useful in creating caches. For example:

function generateUniqueRandNum(range) {
const randomNums = [];

The example above defines a higher-order function, a function that takes in a function as an argument, and/or returns a function. generateUniqueRandNum() takes in a “range” to generate a random number and returns another function generate(). When invoked, generate() first checks if all the possible random numbers within the given range have been cached and returned, if so, it logs this fact and returns, ending potential recursive calls. If there are still unique random numbers that have not been returned, then a random number is generated. A check is performed for the generated number. If the number was generated before, cached, and returned, then that fact is logged and the generate() function recursively calls itself. If the generated number was not cached, then it was not returned and is then cached, pushed in the “randomNums” array, and then returned. This example shows how the inner generate() function uses its closure to access the “range” parameter and the “randomNums” array, in other words, the variable bindings found within the generateUniqueRandNum() function, or the namespace it was defined in. The generate() function is able to access the private state, the “randomNums” array which is used as a cache for previously generated random numbers, and recurse based upon its data, or update it accordingly.

Besides the more commonly implemented single closure, like the example above, multiple closures can also be implemented, that is multiple functions accessing a single private variable.

function counter() {
let currCount = 0;
return {
count: () => currCount++,
reset: () => currCount = 0
};
}

In this example, there are two closures, the count() and reset() methods which both retain references to the private variable “currCount”. In addition, each time the counter() function is invoked, a new counter object is created with its own unique namespace, so that each object returned has access to its own private “currCount” variable binding.

Another way to establish closures is through the use of block scopes. Any block scope can create a closure, including conditional statements or even loops.

function outerFunc() {
let scope = "funcScope";

if (true) {
let scope = "ifScope";

return () => {
console.log(scope);
};
}
}

So far every example has shown how the inner functions retain a reference to the variable bindings of the function namespace it was defined within. However, in this example, although technically the return function is defined within the outerFunc() namespace which has a defined “scope” variable, it is defined within an inner namespace, that of the if conditional statement. The if-statement defines its own “scope” variable, and because the returned function is defined within this namespace, it logs that defined value, “ifScope”.

One important thing to note about closures is that functions maintain a reference to the variable bindings of the namespace they were defined in. When working with the “this” keyword, because it is a keyword and not a variable, careful attention is needed when referring to “this”. So for example:

const sampleObj = {
type: "sampleObj",
props() {
console.log(this.type);
return function() {
console.log(this.type);
}
}
};

The first call to props() logged the “type” for the sampleObj which is the invocation context for props(), or “this” value. Next, an anonymous function is defined and returned which, when invoked, also attempts to log the “type” property for the sampleObj. And although the returned function is defined within the props() namespace which has access to the correct “this” value, because “this” is a keyword and not a variable, it does not have a reference to it via its closure. The solution is of course to use an arrow function that inherits the “this” value of its enclosing props() function.

With this, we have a closer look at closures and their capabilities. Much more can be done with closures, especially when using a functional programming implementation. If you haven’t checked out my previous blog, it provides an introduction to closures and also links to some resources that help to get started.

Flatiron School alumni and Full Stack web developer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store