A singleton is an object-oriented software design pattern which ensures a given class is only ever instantiated once. It can be useful in many different situations, such as creating global objects shared across an application. While JavaScript supports object-oriented programming, it doesn’t provide many simple options to implement this pattern.
The most flexible, albeit somewhat advanced, approach involves using the
Proxy object. The Proxy object is used to define so-called traps. Traps are methods
that allow the definition of custom behavior for certain operations such
as property lookup, assignment etc. The singleton pattern dictates that
the given class can only have one instance. This means that the most
useful trap is handler.construct()
, the trap for the
new
operator.
Turns out the handler
is itself just an object. Apart from
handler.constructor()
, we can use the handler to store the
unique instance of the class we want and if it has been instantiated. In
doing so, we can create a handler object that can be reused for any class
we want to convert into a singleton, while also allowing us to provide
additional traps for any other operations we might want to customize.
Here’s the most basic version of a function that takes a
class
and converts it into a singleton, based on the above
explanation:
const singletonify = (className) => {
return new Proxy(className.prototype.constructor, {
instance: null,
construct: (target, argumentsList) => {
if (!this.instance)
this.instance = new target(...argumentsList);
return this.instance;
};
}) }
And here is a simple practical example to better understand what it does:
class MyClass {
constructor(msg) {
this.msg = msg;
}
printMsg() {
console.log(this.msg);
}
}
= singletonify(MyClass);
MySingletonClass
const myObj = new MySingletonClass('first');
.printMsg(); // 'first'
myObjconst myObj2 = new MySingletonClass('second');
.printMsg(); // 'first' myObj2
In the above example, you can see that the second time
MySingletonClass
is instantiated, nothing happens. This is
due to the fact that an instance already exists, so it is returned instead
of a new object being created. As mentioned, this is a bare-bones
implementation of a singletonify
function. It can be extended
to modify the behavior further or use some of the data passed to the
constructor in later calls to update the instance
it holds.