Close menu

JS Closures Simplified

If you're new to programming, we're not talking about the emotional resolution after a break-up.

Closures exist in languages with first-class functions, like JavaScript. When you first come across them, they might seem abstract and hard to understand very well, but they really are quite simple. With this article, we'll attempt to simplify the concept as much as possible.

Closures are also known as lexical closures, or function closures; and in the name, we get a hint.

A Simplified Definition

A closure stores a function along with an environment.

JavaScript functions have scope; anything defined inside a function's scope exists in and belongs to that scope:

 
var foo = 1; // in global scope
 
function widget() {
// start function scope

  var bar = 2; // in function's scope (i.e. local)

// end function scope
}

Think of everything inside the function's scope as being an environment. This environment is referred to as a lexical environment.

Inside that environment, you might create variables, define functions or instantiate objects etc, that will only exist inside that scope. Nothing outside the function has access to that environment.

If we declare a function within this environment, that function has access to it's own scope, but also the parent's scope - that is, the lexical environment of the function in which it is declared:

 
var foo = 1; // in global scope
 
function widget() {
  var bar = 2; // in widget function's scope

  function doodad() {
      // doodad has it's own lexical environment
      var baz = 3;

      console.log(bar); // logs 2

      // doodad can access it's parent's environment
      console.log(baz); // logs 3
  }
}

So if we return the doodad function from inside the widget function, then assign that value to a variable gadget...

function widget() {
  var bar = 2; // in widget function's scope (i.e. local)

  function doodad() {
      var baz = 3;

      console.log(bar);

      console.log(baz);
  }

  return doodad;
}

var gadget = widget();

gadget(); // logs 2 and 3

gadget is now a symbol that holds not only the function doodad, but also the lexical environment of widget, in which it was created - and that is the closure.

As our examples demonstrate, JavaScript functions, whenever created, create closures.

Application of Closures

Retaining Variable Values

Ironically, unlike emotional closure, where you might return belongings of a significant other to them, the most common use for closures in JavaScript is to do the exact opposite and retain things.

Although garbage collection in JavaScript is a more complicated process, generally, anything defined inside a function is created when the function is called, then deleted from memory when the function finishes execution. We can sort of demonstrate this with the following code:


function widget() {
  const bar = { a: 1 }; // local to widget

  return ++bar.a;
}

widget(); // returns 2
widget(); // returns 2 again
widget(); // returns 2 again

Everytime widget is called, 2 is returned because each call creates the bar object(data="", type="") with (mutable) property a, assigns it a value of 1 then returns the incremement of that value, i.e. 2. At this point the function is finsished executing and so bar is deleted.

Using a closure, we can place bar inside a parent function's lexical environment (i.e. our closure's environment), and return a function to act on bar:


function widget() {
  var bar = { a: 1 }; // local to widget

  return function doodad() {
      return ++bar.a;
  };
}

const baz = widget();
baz(); // returns 2
baz(); // returns 3
baz(); // returns 4

Now the bar is not deleted. This happens because the closure which we have stored in baz, contains not only the function doodad, but the environment which stores the bar object. Because of this, we can continue to increment it's a property. We've used an object here to prove that bar is retained in the environment, but we could have just used a primitive number value instead (great for counters or ID generators etc).

Creating Privacy

Remember, a closure holds a function and an environment - any variables or functions defined in the environment of a closure are only accessible by the function of that closure - i.e. they are private to the closure.


var foo = 0; // global/public

function widget() { // global/public
  var bar = 1; // local to widget/private

  function baz() { // local to widget/private
      return 2;
  }

  return baz;
}

This is a nice way to implement private variables given that JavaScript doesn't have way to create them directly like in other languages (as of 2020).

It Gets A Little More Confusing...

As stated at the beginning, closures can be hard to grasp at first, hopefully, this simplified article has helped clear up the concept - however, as is usually the case with JavaScript, things are a little more complex than they seem and we've basically only explored the most evident form of closures; they are more nuanced in JavaScript, but those nuances are outside the scope of this particular article.