SECRET OF CSS

The Power of Flyweight Design Pattern in JavaScript | by jsmanifest | Jun, 2022


Sharing is caring in JavaScript too

1*dEMEvgK qpwuTkQ8R670KQ
Photo by Lukas: https://www.pexels.com/photo/laptop-computer-showing-c-application-574069/

In JavaScript, we are fortunate to have an automatic garbage collection mechanism built into the language. There are some cases where it is essential to manage the memory ourselves. This is where the Flyweight Design Pattern can come in handy as it intends to share commonalities into objects that clients can benefit from. This is an efficient way to write scalable applications as it benefits us to allow users to consume the least amount of memory usage possible.

In this article, we will be going over the power of the Flyweight Design Pattern in JavaScript and leverage it to create more memory-efficient applications. We will go over the problems that arise and showcase how the flyweight pattern knocks them all away.

If you’ve used a JavaScript library before, there’s a good chance you’ve worked directly on some variation of a flyweight pattern given to you whether it was through a JavaScript library, framework, or even the DOM.

Let’s take a look at this array of objects that represent objects as DOM elements:

If you look at the children array, notice there are three objects that are structurally identical:

0*ry1MjiVA0Lu7CiTa

This is already an issue because if we were to continue this practice and our project grows larger our program will take a big hit in performance because it will create three separate objects in memory although they are all structurally equivalent. Imagine if there were 1,000?

When we go over real examples of the flyweight design pattern this is basically what the flyweight intends to do behind the scenes:

Notice how inputElement is mentioned multiple times.

We will go over examples in different object structures (like classes for example) but ultimately this concept is always applied.

A good way to think of the flyweight pattern is “things being shared.” In our previous example, we shared the inputElement object three times. We minimized the use of memory on at least three occasions.

A common implementation you might encounter frequently is those that implement some sort of get method to retrieve objects in some cache in memory:

This is a great technique to reuse and share previously created objects that don’t need to be recreated since it preserves the user’s memory.

It’s used in many libraries like ts-morph which usually prefix those methods with something like "getOrCreate<the rest of the variable's name>"

Flyweight implementations usually become useful when we add some intrinsic state to it where we can leverage it to make memory-efficient decisions.

In our previous examples, if we look at our CoinCollege class, we can spot our intrinsic state here:

0*pTtA a4ug0YOtUR

We first attempted to grab a previously created Coin instance with the value asked for. If our application created it previously we can avoid the unnecessary re-creation and just return the previous Coin we had stored.

This is a benefit that library authors most often seek.

Another important role in the flyweight pattern is the extrinsic state. These are states that exist outside of the flyweight implementation but would like to work with the flyweight. A common use case is states that are observed by callback functions:

0*7wnpCCZ8mD3G8rI9

With this capability in place, we can halt further creations of Coin if our business logic only applies to the first five coins. This is a great companion to our intrinsic state!

Something that was hard for me to grasp in the earlier stages of my JavaScript development career was the decision-making between prototypal inheritance and factory functions. A mystery that dwelled on me for way too long was figuring out why I often saw functions being created this way:

As opposed to having objects created this way:

In the first example, new instantiations of our Calculator class will inherit and reuse the same properties/methods defined on its prototype. That means these will occur:

0*kkCPdrdyIg02kvmc

In the second example, new instantiations of our makeCalculator factory will not inherit and re-use add and subtract but will instead receive an entirely new add and subtract functions even though they are identical in shape and code size.

0*zHVGfGXkwXiLYflk

They both have their pros and cons in general. But in the context of flyweight, prototypal inheritance is recommended. For all else, however, I always go with the factory function but that’s beyond the scope of this post.

The superagent library showcases the flyweight design pattern in practice using prototypal inheritance in the Request class. Modern programs make frequent requests to do tasks like data fetching. The thing is, these requests cannot be re-used for subsequent requests.

They need to instantiate new instances of some Request object. When programs work with Request objects it’s really unnecessary to make copies of their methods and properties that don’t rely on the current state.

One example of this is the query method on the Request object. The implementation details hardly ever change so there’s no point to create and carry copies to new objects.

I hope you found this to be valuable. Look out for more in the future!



News Credit

%d bloggers like this: