JavaScript Bites: Closure

bamartindev

Brett Martin

Posted on November 16, 2021

JavaScript Bites: Closure

TLDR: Closure is the concept of storing a function and its environment together. When you create a function, it stores the functions local environment and its outer environment together. If you are ever confused about what value will be present, understand what value existed when the function scope was created!

Formal Definition

If you were to look up what a closure is, Wikipedia's definition has this to say in the first two lines:

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.

That is a bit of a dense definition, but its not a complex as it seems at first glance! This article aims to explain what this means, bit by bit, so you can use closures with confidence.

Scoping

I first want to touch on what scoping means in JavaScript. Before ES6, JavaScript only had Global Scope and Function Scope. You have probably seen how variables are accessible based on what scope they were declared in. Here is an annotated example:

// Variable declared at the global scope.
var globalVariable = 'Neat';

function func() {
  // Variable declared in function scope.
  var funcVar = 12;

  console.log(globalVariable);
}

console.log(funcVar);
func();
Enter fullscreen mode Exit fullscreen mode

If you were to execute the above code, you would get a ReferenceError: funcVar is not defined error. If you remove the console.log(funcVar); line, the output would be Neat. The reason for this is that scopes can only reference variable declared in their own scope (local) and any outer scopes relative to the current scope. In this case, the scope in func() can access the outer scope (global) to get the value of globalVariable, however the global scope does not have access to the scope created for func() so it cannot access the funcVar variable. One more example to show how inner scopes can access values in outer scopes.

var globalVar = 'Hello';

function func() {
  var innerVar = 'World';

  function innerFunc() {
    var name = 'innerFunc';

    console.log(`${globalVar} ${innerVar}, from ${name}`);
  } 
  innerFunc();
}

func();
Enter fullscreen mode Exit fullscreen mode

Executing the above will show Hello World, from innerFunc in the console. We can see that innerFunc() has access to its local scope, the scope of func() and the global scope.

Closure

The example above is actually a closure! It represents the second part of the Wikipedia definition, Operationally, a closure is a record storing a function together with an environment. In this case, the function is innerFunc() and the environment that is being stored is the local scope along with all of the outer scopes present at the time of function creation.

Thats it! If you have been writing functions, you have been creating closures this whole time!

Whats the Big Deal

The reason this can be a confusing topic is that closures can enable a handful of different patterns and ideas in JavaScript, even if they don't seem related at all. So here are some quick examples of things that are possible because of closures:

Access Data Through Interface

Say you wanted to create a simple counter with a variable representing the current count, and four functions: add, subtract, reset, show.

let count = 0;

const add = () => {
  count = count + 1;
};

const subtract = () => {
  count = count - 1;
};

const reset = () => {
  count = 0;
};

const show = () => {
  console.log('Count: ', count);
};
Enter fullscreen mode Exit fullscreen mode

If you were to use these functions to add and show, like

add();
add();
add();
add();
show();
Enter fullscreen mode Exit fullscreen mode

you would get Count: 4. The issue is that if I were to throw in count = 0; right before the show() it would show Count: 0! We are operating on a variable that any scope can access and modify, since it is global, and that is dangerous. Something can accidentally mess with count and cause a headache of a bug. This can be written in a different way:

const mkCounter = () => {
  let count = 0;

  const add = () => {
    count = count + 1;
  };

  const subtract = () => {
    count = count - 1;
  };

  const reset = () => {
    count = 0;
  };

  const show = () => {
    console.log('Count: ', count);
  };

  return {
    add,
    subtract,
    reset,
    show
  };
};
Enter fullscreen mode Exit fullscreen mode

This code is very similar, but you can see that we have declared it inside of a new function called mkCounter that defined the count variable locally to its scope. At the end, we return an object that exposes the four functions but not the count variable, however since all of these functions are defined inside the mkCounter scope, the closing environment for all of them contain count! Here is how it would be used:

const counter1 = mkCounter();
const counter2 = mkCounter();

counter1.add();
counter1.add();
counter1.add();
counter1.subtract();

counter2.subtract();
counter2.subtract();

counter1.show();
counter2.show();
console.log(counter1.count);
Enter fullscreen mode Exit fullscreen mode

which will give the output of:

Count: 2
Count: -2
undefined
Enter fullscreen mode Exit fullscreen mode

Awesome, so not only can we not access the count as shown by the last line, each counter has its own count in their own environment to work with!

Partial Application

Edit: Updated this section thanks to @zaferberkun and @peerreynders in the comments!

Another closure example that I use all the time is partial application. A simple example could be formatting a log with some data that you don't want to set every time you invoke the function:

function logger(route, message, showDate) {
  const header = showDate ? `${new Date().toISOString()} | ${route}` : route;
  console.log(`${header} | ${message}`);
}

function mkLogger(route, showDate = false) {
  // Implement "partial application" with the values
  // in the closure
  return (message) => logger(route, message, showDate);
}

Enter fullscreen mode Exit fullscreen mode

Then you can use the function like:

const docLogger = mkLogger('DOCS', true);

docLogger('This is my log message');
docLogger('Another log message');
Enter fullscreen mode Exit fullscreen mode

with the output of:

2021-11-15T23:55:26.672Z | DOCS | This is my log message 
2021-11-15T23:55:26.672Z | DOCS | Another log message 
Enter fullscreen mode Exit fullscreen mode

This is nice because you can initialize things like the route and if you want to display the date when the program starts, then pass the simple docLogger function to other parts of the application that need to use it instead of calling something like logger('DOCS', 'This is my log message', false) every time you want to use it.

Other Uses

I just wanted to mention some other use cases that you can explore as well: Memoization, Singleton, Event Listeners.

Conclusion

Hopefully the concept of closure isn't too complex any more! If you do have any questions please let me know and I will do my best to address them and refine the article for clarity.

💖 💪 🙅 🚩
bamartindev
Brett Martin

Posted on November 16, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Common Git Commands and what they do
beginners Common Git Commands and what they do

January 24, 2023

Getting Started with TailwindCSS
beginners Getting Started with TailwindCSS

January 23, 2022

JavaScript Bites: Closure
beginners JavaScript Bites: Closure

November 16, 2021