4 min read

Closing in on closure

Closing in on closure

There is a familiar tenet in programming.

When the function has completed running, the variables are disposed.

This is pretty close to our hearts as programmers.

So naturally, JavaScript comes in shakes things up like a Taylor Swift song, because that's what JavaScript does.

In JavaScript, you can have variables that stay around long after the function that they're created in returns. When a function has an inner function that references variables in the outer function's scope, you create what is known as a closure.


An Example

function composeMessagesTo(firstName, lastName) {
  let fullName = `${firstName} ${lastName}`;
  
  return {
    sayHello: () => {
      return `Hello, ${fullName}`;
    },
    sendGift: (yourFirstName, yourLastName) => {
      return `How marvelous, ${fullName}! ${yourFirstName} ${yourLastName} has sent you a gift!`;
    },
    askToBorrow: (thing) => {
      return `${firstName}, could I borrow ${thing}?`;
    }
  }
}

let messagesForJordanRudess = composeMessagesTo('Jordan', 'Rudess');

console.log(messagesForJordanRudess.sayHello());
console.log(messagesForJordanRudess.askToBorrow('your mad skills as a world class musician of arguably the best progressive metal band on earth?'));

Naturally, this would log:

"Hello, Jordan Rudess"
"Jordan, could I borrow your mad skills as a world class musician of arguably the best progressive metal band on earth?"

Which makes perfect sense reading the code. However, let's think back to that age old tenet:

When the function has completed running, the variables are disposed.

By that logic, when we run let messagesForJordanRudess = composeMessagesTo('Jordan', 'Rudess'); we would lose access to firstName, lastName, and fullName; however, as you can tell by the logs, the variables still exist!

When the function composeMessagesTo returns, there are still references to functions (through messagesForJordanRudess) that have access to the variables firstName, lastName, and fullName; because of this, those variables are not disposed. In fact, none of the variables in the function will be disposed of until all references to those functions are disposed.

Fun Fact: variables that are neither local variables or parameters to a function are sometimes referred to as free variables.

-Thanks to @jdan for that bit of knowledge


Why is this useful?

This can be quite useful in a number of cases!

Sometimes, you want to curry a function. The classic example is making an adder:

function addToNum(num) { 
  return (addToNum) => {
    return num + addToNum;
  }
}

let addToTwelve = addToNum(12);
console.log(addToTwelve(2)); // 14
console.log(addToTwelve(-10)); // 2

Leveraging closures can help you clean up your code immensely by removing repetition (remember, DRY code is good code!) across your applications.

More practically towards views, let's say you are going through a signup wizard, with many fields to check.

function addErrorToList(id, message) {
  let errorListEle = document.getElementById(id);
  if (!id) throw "List does not exist";

  let newError = document.createElement("li");
  let newErrorMessage = document.createTextNode(message);

  newError.appendChild(newError);
  errorListEle.appendChild(newError);
}

function checkSignupWizard() {
  let testForm = document.getElementById("wizard");

  let userNameInput = testForm.querySelector("#username");
  if (!userNameInput) { 
    addErrorToList("wizard-form-errors", "You must include a username");
  }

  let passwordInput = testForm.querySelector("#password");
 
  if (!passwordInput) { 
    addErrorToList("wizard-form-errors", "You must include a password");
  }

  // etc  
}

Say that you use addErrorToList hundreds of times throughout your application -- this can grow very confusing (and has some performance issues) and hard to maintain as your views change; what if you changed wizard-form-errors to wizard-errors? You'd have to change it everywhere!

Instead, we can use closures to refactor that to be:

function makeErrorsFor(id) {
  let errorListEle = document.getElementById(id);
  if (!id) throw "List does not exist";

  return (message) => {
    let newError = document.createElement("li");
    let newErrorMessage = document.createTextNode(message);

    newError.appendChild(newError);
    errorListEle.appendChild(newError);
  };
}

function checkSignupWizard() {
  let testForm = document.getElementById("wizard");
  let addToErrorList = makeErrorsFor("wizard-form-errors");

  let userNameInput = testForm.querySelector("#username");
  if (!userNameInput) { 
    addToErrorList("You must include a username");
  }

  let passwordInput = testForm.querySelector("#password");
 
  if (!passwordInput) { 
    addToErrorList("You must include a password");
  }

  // etc  
}

Now, you are only querying that element once each time you run checkSignupWizard, and you have easier to maintain code!

Other times, you've got an asynchronous component that has to update a view on your page!

function dataToHTML(someData) {
  let htmlStr = "";
  // write HTML out judging on data
  return htmlStr;
}

function refreshPage() {
  let content = $("#content");

  ajaxGet("/my_data.json").then((result) => {
    content.html(dataToHTML(result));
  });
}

refreshPage();

For the sake of demonstration, we'll pretend that the the ajaxGet function is an asynchronous function that will take (for arguments sake) 2 seconds; however, the function will finish running long before that AJAX request finishes. When the callback on the ajaxGet function is called, it will still have access to content. Closures and asynchronous code go hand in hand.

Before, we made a quasi-function factory leveraging closures, but we can make one better.

function makeResourceFactory(dataType) {
  let route = `/api/${dataType}`;

  return {
    get: (id) => {
      return ajaxGet(`${route}/${id}`);
    },
    getAll: () => {
      return ajaxGet(`${route}`);
    },
    search: (searchDescriptor) => {
      let terms = Object.keys(searchDescriptor).map((key) => {
  return `${key}=${searchDescriptor[key]}`;
});
      let searchQuery = terms.join("&");

      return ajaxGet(`${route}?${searchQuery}`);
    },
    create: (newContent) => {
      return ajaxPost(`${route}`, newContent);
    },    
    update: (id, updateContent) => {
      return ajaxPut(`${route}/${id}`, updateContent);
    },
    delete: (id) => {
      return ajaxDelete(`${route}/${id}`);
    }
  };
}

const carFactory = makeResourceFactory("cars");
const userFactory = makeResourceFactory("users");

All of those functions makes use of the route variable, which is defined in the original function.

Closures are powerful, but leveraging them allows you to create more powerful, clean, and concise JavaScript. With an understanding of closures, you will be able to approach more complex topics in JavaScript, such as Promises much more easily.