Last active
August 19, 2025 07:25
-
-
Save dotproto/9b3a60ea149cdb95774496e9db6ac861 to your computer and use it in GitHub Desktop.
An attempt at using ES6 classes to implement the Singleton design pattern.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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