DEV Community

Brett Martin
Brett Martin

Posted on • Originally published at catstache.io

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.

Discussion (5)

Collapse
zaferberkun profile image
zaferberkun

Strictly speaking, I don't believe your example of partial application is any different than the preceeding "counter" example--its just a higher order function that creates a closure when it returns the inner function. Partial application would be creating a logging function, and then creating another logging function that returns this existing logging function with some or the args already applied and the missing args supplied by the new function, thus the partial application or an already existing logging function. Correct me if im wrong or misunderstood your example.

Collapse
bamartindev profile image
Brett Martin Author

Hi zaferberkun - thanks for the comment!

I can see what you are saying - I think I need to come up with a better example in this case to show the partial application, as the underlying console.log only takes the one argument.

How about a simple add example in its place, where the example of the closure is in the partial function definition:

const partial = (fn, arg) => {
  return (...args) => {
    return fn(arg, ...args);
  };
};

const add = (x, y) => x + y;

const add10 = partial(add, 10);

console.log(add10(5));
Enter fullscreen mode Exit fullscreen mode
Collapse
peerreynders profile image
peerreynders • Edited on

I think I need to come up with a better example in this case to show the partial application, as the underlying console.log only takes the one argument.

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

logger('LIBS', 'Direct log message', false);
docLogger('This is my log message');
docLogger('Another log message');

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
Thread Thread
bamartindev profile image
Brett Martin Author

Thanks for writing this up - ill add it to the main article and shout you out!

Collapse
zaferberkun profile image
zaferberkun

Hi Brett,

I like that! Also, after some consideration, I think I was actually wrong and your original example (and the one by peerreynders below) is a perfectly valid example of partial application.

What threw me was the use of an "intermediary" make function to create the partial. What I was expecting was that the function itself, logger in this case, could be applied partially "in place". You would have to add "undefined" arg checks, but that way you could do full or partial application without "intermediary" maker functions for each arg permutation. Of course, if you're planning using composition, you'll need to generate a partial that only takes one arg (which your logger make example is doing). Anyway, sorry about that, you can disregard my original post if you want.

Btw, great writing, hope you'll tackle some more functional programming concepts! I'm going to have to backtrack and read the ones you've already written.