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
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;
}
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);
}
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];
}
}
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!
Top comments (0)