Skip to content

Instantly share code, notes, and snippets.

@dotproto
Last active August 19, 2025 07:25
Show Gist options
  • Select an option

  • Save dotproto/9b3a60ea149cdb95774496e9db6ac861 to your computer and use it in GitHub Desktop.

Select an option

Save dotproto/9b3a60ea149cdb95774496e9db6ac861 to your computer and use it in GitHub Desktop.
An attempt at using ES6 classes to implement the Singleton design pattern.
class Singleton {
static #instances = new WeakMap();
/**
* @param {Boolean} useSharedThis Specifies whether or not `this` should be the singleton itself.
* If `true`, the Singleton class' constructor will return the singleton associated with the current
* prototype being instantiated. As a direct result, instance fields on the inherting class will be
* re-set on the singleton every time the descendant class is instantiated. This may created
* unnecessary and avoidable work. The main advantage of selecting `true` is that the sub-class does
* not have to explicitly return the singleton, since class constructors implicitly return `this`.
* If `false`, the value of `this` in the sub-class's constructor will not be modified. This option
* helps ensure that any fields set at construction time cannot conflict with existing values on the
* singleton. The main disadvantage of this approach is that the sub-class must handle returning the
* singleton from it's own constructor. This can be easily done by adding the following line after
* the `super()` call in the constructor: `if (!Singleton.is(this)) return Singleton.get(this);`
*/
constructor(useSharedThis) {
if (typeof useSharedThis !== 'boolean') {
throw new TypeError('When extending Singleton, the sub-class MUST call `super()` with a single Boolean to specify whether or not `this` should be set to the singleton instance.');
}
if (!Singleton.has(this))
Singleton.#instances.set(Object.getPrototypeOf(this), this);
if (useSharedThis && !Singleton.is(this))
return Singleton.get(this);
}
static is(that = this) {
return that === Singleton.get(that);
}
static has(that = this) {
return Singleton.#instances.has(Object.getPrototypeOf(that));
}
static get(that = this) {
return Singleton.#instances.get(Object.getPrototypeOf(that));
}
}
// Example
class A extends Singleton {
value;
constructor() {
super(true);
// WARNING: Every time you call `new A()`, the constructor will re-execute and the
// following line will set the singleton's `val` value to a new random number.
// Depending on your needs, this behavior may or may not be desierable.
this.val = Math.random();
}
}
class B extends Singleton {
value = Math.random();
constructor() {
super(false);
// WARNING In order for `new B()` to return the same singleton every time it's
// called, we must manually return the singleton object.
if (!Singleton.is(this)) return Singleton.get(this);
}
}
// Instantiate some stuff and see what objects we get back. All B instances returned
// in both of the below console.logs calls should have the same `value` property.
console.log(
a1 = new A(),
a2 = new A(),
b1 = new B(),
b2 = new B(),
);
console.log(b3 = new B(), b2, b3 === b2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment