Microblog: using closures to create JavaScript factories

shaileshcodes

Shailesh Vasandani

Posted on December 25, 2020

Microblog: using closures to create JavaScript factories

Hi everyone! In today's Microblog post, we'll be looking at JavaScript closures and how you can use them to make factories.

First, though — why learn about this technique? Well, even though many people dive straight into frameworks like React and Angular, it's always good to understand the fundamental vanilla JavaScript underlying those frameworks. As a result, you'll be able to do more both with and without the frameworks supporting you. Now, onto closures:

What are closures?

Good question. At their core, closures are simply an enclosed scope inside a function. They allow an inner function to access the variables in an outer function. A super simple example would look something like this:

      const addTo = (numberOne) => {
        return (numberTwo) => {
          return numberOne + numberTwo;
        }
      }

      const addToFive = addTo(5);
      const addToTen = addTo(10);

      addtoFive(3); // => 8
      addToTen(3); // => 13
Enter fullscreen mode Exit fullscreen mode

When the addTo function is called with a parameter, it returns another function that has access to the numberOne variable; this returned function is the closure. Both addToFive and addToTen each have their own unique scope, where the variable numberOne equals 5 and 10 respectively. As a result, when calling those functions with 3 as a parameter, they give the expected results of 8 and 13. Now, onto factories:

What are factories?

Factories are generally used in testing to create objects without creating the full object declaration inline. A simple example might look this:

      /* User {
        {string} firstName
        {string} lastName
        {number} age
      }
      const createUser = (userObj) => {
        // create mock user
        let user = {
          firstName: "John",
          lastName: "Doe",
          age: 21
        };

        Object.keys(user).forEach((userKey) => {

        });

        return user;
      }
Enter fullscreen mode Exit fullscreen mode

This allows us to scope our testing to be more relevant to the tests we perform.

      // WITHOUT FACTORY
      const returnsFalseForMinors = () => {
        // irrelevant data in test
        let user = { firstName: "John", lastName: "Doe", age: 17 });

        console.assert(functionToTest(user), false);
      }

      // WITH FACTORY
      const returnsFalseForMinors = () => {
        let user = createUser({ age: 17 });

        console.assert(functionToTest(user), false);
      }
Enter fullscreen mode Exit fullscreen mode

Factories and closures, together?

When we use factories together with closures, we're able to dynamically generate useful functions that don't have to take too many parameters. Here's an example from the codebase for my photography page, where I needed to add and remove different classes for large amounts of objects:

      // closure and factories, working together
      const removeAndAdd = (remove, add) => {
        return (el) => { 
          el.classList.remove(remove);
          el.classList.add(add);
        }
      }

      // methods generated by the factory for use later
      const leftToCenter = removeAndAdd("left", "center");
      const centerToRight = removeAndAdd("center", "right");
      const rightToCenter = removeAndAdd("right", "center");
      const centerToLeft = removeAndAdd("center", "left");

      // ...

      const movePrev = () => {
        if (currentIndex <= 0) return;
        else {
          centerToRight(images[currentIndex]);
          leftToCenter(images[--currentIndex]); // decrement inline
          labelText.innerHTML = (currentIndex + 1) + "/" + numImages;
          labelTitle.innerHTML = altText[currentIndex];
        }
      }

      const moveNext = () => {
        if (currentIndex + 1 >= numImages) return;
        else {
          centerToLeft(images[currentIndex]);
          rightToCenter(images[++currentIndex]); // increment inline
          labelText.innerHTML = (currentIndex + 1) + "/" + numImages;
          labelTitle.innerHTML = altText[currentIndex];
        }
      }
Enter fullscreen mode Exit fullscreen mode

As you can see, by using a closure as a function factory, I was able to avoid repeating calls to each element's classList, making my code more readable and semantic in the process.

I hope this short post gives you an idea of the power of closures in JavaScript, and I'm hoping to make a longer post further down the line detailing the most powerful ways these can be used. Make sure to follow me to be notified when that post drops.

If you found this post useful, please consider buying me a coffee. Until next time!

💖 💪 🙅 🚩
shaileshcodes
Shailesh Vasandani

Posted on December 25, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related