Closures are an important topic in JavaScript that cause developers a disproportionate amount of worry. If you’ve written more than a little JavaScript, you’ve probably created a closure without realizing. Does this look familiar?

// ...

ClickCounter.prototype.bindEventHandlers = function() {
  var self = this;
  var button = document.getElementById('btn');
  button.addEventListener('click', function() {
    self.buttonClickCount++;
  });
};

That uses closures!

Put succinctly, closures are inner functions that use variables declared in their outer functions. This feature, however, is built upon lower-level concepts that often go overlooked. This is unfortunate, since we can use this foundation to help build better mental models that facilitate debugging and inform code design. In this post, I would like to explore these foundational concepts first, returning to closures after with appropriate context.

Function scope and hoisting

JavaScript uses function scoping, meaning that variables defined within a function are accessible anywhere in that function, including nested functions. Synonymously, functions have access to all variables defined within them and in any parent functions. This contrasts with block scope, where variables defined in blocks—like loops and try statements—are unavailable outside their blocks.

function firstFunctionScope() {
  var frog = 'frog';

  function inner() {
    var fish = 'fish';

    console.log(frog); // "frog";
    console.log(fish); // "fish";
  }

  console.log(frog);   // "frog";
  console.log(fish);   // ReferenceError: fish is not defined
}

function secondFunctionScope() {
  var bird = 'bird';

  console.log(bird);   // "bird";
  console.log(frog);   // ReferenceError: frog is not defined
}

Furthermore, variables in JavaScript are hoisted, a term used to describe the apparent “hoisting” of their declarations to the top of the function. This is illustrated in the following example, where we refactor the first function into the second by shifting the declaration of each variable to the top of the function. Note that all subsequent assignments and expressions preserve their order. Ultimately, this means we can access a function’s variables at any point within scope, regardless of their position.

function declarations() {
  var horse = 'horse';

  for (var i = 0; i < 3; i++) {
    var hare = i * 2;
  }

  var hat = function() {};
  function hen() {};
}

// functionally equivalent
function hoistingDeclarations() {
  function hen() {};
  var horse, i, hare, hat;

  horse = 'horse';

  for (i = 0; i < 3; i++) {
    hare = i * 2;
  }

  hat = function() {};
}

This can cause unexpected behavior at times, since a hoisted variable may be called before its value is assigned.

foo();
bar(); // Uncaught TypeError: bar is not a function

function foo(){}        // hoisted
var bar = function(){}; // hoisted but not assigned

Hoisting and closures leverage a similar mechanism called the execution context.

Execution context and the scope chain

When a function is called in JavaScript, it creates an execution context containing the following information:

  • The value of this
  • A list of all its variable declarations (the variable object)
  • The list of all its parents’ variable objects (the scope chain)

This information is collected before the body of the function is executed. Hoisting occurs during this stage when variables are aggregated in the variable object.

After the creation stage, the function continues normally where it uses and assigns its many variables. When it encounters a variable, it checks the variable object for that variable’s location in memory.

If, however, a function does not find the variable in its own variable object, it travels down the scope chain, checking each of its parent’s variable objects. It will continue searching its outer functions until it reaches the global context (the code’s entry point, e.g. window), after which it throws a ReferenceError.

Allow us to explore this concept by invoking the function from an earlier example.

function declarations() {
  var horse = 'horse';

  for (var i = 0; i < 3; i++) {
    var hare = i * 2;
  }

  var hat = function() {};
  function hen() {};
}

declarations('param', 10);

Doing so, we could represent its execution context as the following object,

var theoreticalExecutionContext = {
  this: window,
  variableObject: {
    arguments: {
      0: 'param'
      1: 10,
      length: 2
    },
    hen: {},        // reference to function hen
    hare: 4,
    hat: {},        // reference to anonymous function
    horse: 'horse',
    i: 3
  },
  scopeChain: []    // this obj's variableObject, window's variableObject
};

Closures

With a rough idea of how execution contexts and the scope chain work, we are able to see the truth behind closures. Now we understand closures as a concept in addition to just a fancy word.

Event handler callbacks

Let us revisit our first example, rewritten slightly, to clarify what is going on under the hood when binding our event handler.

ClickCounter.prototype.bindEventHandlers = function() {
  var self, button;

  function handler() {
    self.buttonClickCount++;
  }

  self = this;
  button = document.getElementById('btn');

  button.addEventListener('click', handler);
};

When called, our function first creates its execution context. Here, the value of this is the object to which the function belongs—an instance of ClickCounter. Its variable object contains keys to two variables, self and button, and an inner function, handler.

The function body then executes, assigning to self our class instance and assigning to button an object representing a button element on document (the web page loading the script). Finally, it calls button.addEventListener, which sets up an event listener that will call handler when the button is clicked.

Later, when the button is clicked, it calls handler. However, since the button is calling the function (not of the instance of ClickCounter), the value of this in handler’s execution context is the button. If we would have used this in the callback instead of self, we would have incorrectly incremented buttonClickCount on the button.

As written, handler looks in its own variable object for a reference to self. Failing to locate it there, it traverses its scope chain and finds it in its parent’s variable object. Doing so correctly increments the property on our class instead of the button.

Private variables

Another common use of closures is the creation of “private” variables. The example below, an example of the revealing module pattern, returns an object that only contains pointers to the variables and functions the author wishes to expose. Thanks to closures, these exposed functions may still access variables and functions persistent long after their defining function returned.

function SecretiveClass() {
  var privateVariable = 10;
  var publicVariable = 20;

  function privateMethod(public, private) {
    return public * private;
  }
  function publicMethod(public) {
    if (public) {
      publicVariable = public;
    }
    return privateMethod(publicVariable, privateVariable);
  }

  return {
    publicVariable: publicVariable,
    publicMethod: publicMethod
  };
}

var instance = SecretiveClass();
typeof instance;                 // "object"
typeof instance.privateVariable; // "undefined"
typeof instance.privateMethod;   // "undefined"
typeof instance.publicVariable;  // "number"
typeof instance.publicMethod;    // "function"

Summary

To summarize, every function stores a variable object containing a reference to all the functions and variables defined within it. It also sets up a scope chain, which contains the variable object of its parent and any other ancestors it may have. When accessing a variable, it checks its own variable object for a variable with that name. If it doesn’t find it there, it traverses its ancestors’ variable objects via its scope chain. These variable objects persist as long as there is a reference to them.

The examples above are only two of the many applications of closures in JavaScript. Hopefully with a basic understanding of scope, execution contexts, and the scope chain, you will be able to identify, understand, and debug closures more easily in your own code and in any code you may come across.

Bibliography

For additional exploration into these topics, check out the following links, which provided the basis of this post: