Getting Closure(s)

Posted 2013-10-11 05:48 PM GMT

Understanding Closures in JavaScript

When I was first learning about closures in JavaScript, I was very confused. First, I thought it was just Google's open source JavaScript library. I looked on StackOverflow and found a few discussions that were somewhat helpful, but I left more confused than when I started. Then I found a few blog posts, and they were more helpful, but I still wasn't sure if I understood.

Since then, I have worked with closures quite a bit, and I have realized that they are far more simple than I had originally thought.

A JavaScript function creates a new scope. Functions can be defined within functions. The inner functions are a part of the outer function's scope, and have access to the variables defined in the outer function. The implications of this make closures useful.

Scope

In this context, scope refers to the part of your code where a variable can be used.

// This is the global scope. Variables declared in this scope // are available in all other scopes. var x = 1; function outer(y) {
// This is the outer function's scope. Variables declared here are // available in the outer function and the inner function, but not // in any other scope, including the global scope. function inner(z) {
// This is the inner function's scope. Variables declared here are // only available in the inner function. console.log('x is:' + x); // prints "x is: 1" console.log('y is:' + y); // prints "y is: 5" console.log('z is:' + z); // prints "z is: 6"
} inner(x + y); // x=1, y=5
} outer(5);

The global scope is like the O Negative blood type—the universal donor. Every other scope can use variables defined in the global scope, but the global scope can only use its own variables.

The inner function is like the AB Positive blood type—the universal recipient. The inner function can use variables defined in all parent scopes (in this case, the outer function scope and the global scope), but no other scope can use the variables defined in the inner function.

How it's useful

Sadly, every article I've read on closures stops with an example like the one above. You may have a vague idea about what a closure is, but you have no idea why it is useful. I think a real-world example helps clarify what a closure is and how it is useful.

Avoiding global variables is widely regarded as a best practice in JavaScript. There are a few methods of doing this, and using a closure is one of them. In this example, I will allow a user to undo a "delete" action if they click the "undo" button within 5 seconds. I'm using jQuery to simplify a few things.

function setupEventListeners() {

  // This variable is the reason we want to use a closure,
  // because it needs to be available in multiple functions,
  // like a global variable would be.
  var undoTimer;

  // Cache a reference to the item container and undo button.
  var $itemContainer = $('#item-container');
  var $undoButton = $('#item-container .undo');

  // Setup click event listeners
  $('#item-container').on('click', function(evt) {
    var $target = $(evt.target);

    if ($target.hasClass('delete')) {

      // IMPORTANT:
      // When the delete button is clicked, assign a setTimeout to
      // the closure variable `undoTimer`. Wait 5 seconds before actually
      // deleting the item.

      undoTimer = window.setTimeout(deleteItem, 5000);

      // Show the undo button so it can be clicked
      $undoButton.addClass('show');

    } else if ($target.hasClass('undo')) {

      // IMPORTANT:
      // If the undo button is clicked before the 5 seconds is up, clear the
      // timeout (using the closure variable `undoTimer`), so the item does not
      // get deleted

      window.clearTimeout(undoTimer);

      // Hide the undo button.
      $undoButton.removeClass('show');
    }
  });

  // Use this function to delete the item.
  function deleteItem() {
    $.ajax({ url: '/deleteItem' }).then(function() {
      $itemContainer.remove();
    });
  }
}

setupEventListers();

The setupEventListeners function is only called one time, but the undoTimer, $itemContainer, and $undoButton variables are available each time the click listener's callback function is called, and each time the deleteItem function is called. The fact that these variables are "kept alive" after the original setupEventListeners function finishes execution is what is so powerful about closures. It's really that simple.

PS This same power exists in any language with lexical scoping.