th.
A roll of raffle tickets
Photo by Fancycrave on Unsplash.

Code IRL: Javascript closures

01 May 2019 • 4 min read

Sometimes it's hard to find a real world example for certain code concepts. Here's an example of using Javscript closures in the wild.

A Brief Introduction to Closures

As a starter, a Javascript closure is a function that returns another function. The returned function can reference variables from the parent function, even if those variables are considered out of scope. As an example, look at this little function:

const makeAdder = function (startingNumber) {
  return function (number) {
    return number + startingNumber;
  };
};

const myAdder = makeAdder(10);
console.log(myAdder(2));

Here I'm defining a closure named adderFactory that takes in a startingNumber and returns a function that adds a parameter named number to that startingNumber. When I call adderFactory(10), I'm "enclosing" the value of startingNumber in my function (which in this case is the value 10).

When I call myAdder(2), I can access that startingNumber even though that variable is techincally out of scope. Why? When my adderFactory returned that adder function, it enclosed the value of 10 with it. I could make a new adder:

const myNewAdder = makeAdder(100);
console.log(myAdder(20));

Even though these were made with the same factory, because I've created two distinct functions, two distinct (enclosed) values for startingNumber will exist.

If you want to read more on closures, W3 Schools and MDN docs both have good articles that explain how they work in more detail.

Filtering Tickets

I was recently doing some array manipulation and needed to do some complex filtering. I had two arrays with tickets, and each ticket had an id and a number property (representing different data). I needed a way to filter my oldTickets array to only include tickets found in my newTickets array. I chose to use the built in Array .filter method and iterate over the two arrays.

Note: I realize that the big O here is n², which is pretty inefficient. For what I was working on, that was ok.

Here's the starting code that I wrote in order to filter my arrays (included are some declarations for the ticket arrays, to demonstrate what the data looks like):

const oldTickets = [
  {id: 1, number: '2'},
  {id: 2, number: '3'},
];

const newTickets = [{id: 1, number: '2'}];

const tickets = oldTickets.filter((oldTicket) => {
  const isOldTicketFound = newTickets.find((newTicket) => {
    return (
      oldTicket.id === newTicket.id && oldTicket.number === newTicket.number
    );
  });

  return isOldTicketFound;
});

The .filter method expects a boolean to know if it should keep the current item or discard it. The .find method looks in the newTickets array, checks if the current oldTicket is found, and returns true or false accordingly.

Note: both .filter and .find iterate over the tickets, which is where oldTicket and newTicket come from.

Identifying a Pattern

After writing the code, I recognized that I had the ticket comparison code in multiple places. I wanted to write a generic ticket equality checker, but I realized that I needed to be able to access both newTicket and oldTicket. While this could be solved by writing a function that takes two arguments, newTicket and oldTicket, I decided to make use of closures instead. This would allow me to declare a function with oldTicket "saved" inside of it.

I started off by writing my closure function:

const makeTicketEqualityChecker = (oldTicket) => {
  return (newTicket) => {
    return (
      oldTicket.id === newTicket.id && oldTicket.number === newTicket.number
    );
  };
};

This function takes in an argument of oldTicket and then returns a function. The inner function references oldTicket from the outer function, so it retains the original oldTicket argument for executing later.

Using the Closure

After writing that closure, I could make use of it in my original filter function:

const tickets = oldTickets.filter((oldTicket) => {
  const isEqualToOldTicket = makeTicketEqualityChecker(oldTicket);

  const isOldTicketFound = newTickets.find((newTicket) =>
    isEqualToOldTicket(newTicket),
  );

  return isOldTicketFound;
});

On line 2 I assign the return function of ticketEqualityFactory to isEqualToOldTicket. By this point I've enclosed the value of oldTicket inside of isEqualToOldTicket. When I call isEqualToOldTicket in my .find function, it takes in the value of newTicket, compares it against the "saved" value of oldTicket, and returns if they are equal or not.

Reply to this post on Twitter
© 2024 Trevor Harmon