Explain Codes LogoExplain Codes Logo

Simplest/cleanest way to implement a singleton in JavaScript

javascript
singleton
es6-classes
thread-safety
Alex KataevbyAlex Kataev·Oct 10, 2024
TLDR

In JavaScript, a singleton can be efficiently implemented using an IIFE (Immediately Invoked Function Expression) that encapsulates a single instance reference and exposes a method to access it. This setup guarantees that our Singleton.getInstance() always returns the same object, resulting in one single instance throughout the entire application.

const Singleton = (function() { let instance; function createInstance() { // Define private members here, handle them with care! const secret = 'Singleton'; const whisper = () => { console.log('I solemnly swear that I am up to no good'); }; return { // Public methods and properties spillBeans: whisper }; } return { getInstance: function() { // Being lazy never felt so performant! if (!instance) { instance = createSnapshot(); // Freeze it to preserve its solitary existence Object.freeze(instance); } return instance; } }; })(); // Usage const example1 = Singleton.getInstance(); const loneWolf = Singleton.getInstance(); console.log(example1 === loneWolf); // true, only one may rule them all!

A fortress of solitude: Implementing a robust singleton

Let's delve into some potent strategies to fortify our singleton, ensuring it remains as undivided as Tormund's love for giants:

Leaning on ES6 classes and static instance property

Incorporating ES6 classes can make your singleton look sleek and modern. Plus, it allows you to juggle private members using # and ensures a single instance with nothing but a static method:

class Singleton { static instance; #secretData = 'Singleton'; constructor() { // The 'one ring to rule them all' check if (Singleton.instance) { return Singleton.instance; } Singleton.instance = this; // Optional instance freeze: cold as ice, solid as rock Object.freeze(this); } spillBeans() { console.log(this.#secretData); } }

Every new instance will be our steadfast singleton, sharing the same secret over instances:

// Usage const test1 = new Singleton(); const test2 = new Singleton(); console.log(test1 === test2); // true, one for all and all for one!

ES6 Modules: The singleton's best friend

ES6 modules are like peanut butter to singleton's jelly, a dynamic duo. Exporting an instance from a module ensures that wherever it's imported, it’s the same instance doing the tour:

// Our famous singleton.js const Singleton = { secret: 'Singleton', whisper() { console.log('Expecto Patronum'); } }; // It's cool to be frozen Object.freeze(Singleton); export default Singleton;

Anytime you import, it's the same old reliable singleton at your service:

// Either Hogwarts or the Shire, the singleton remains one! import Singleton from './singleton';

Be mindful of singleton surprises: Thread safety and lazy instantiation

The singleton pattern's perks come with some catch involving thread safety and lazy instantiation. JavaScript is single-threaded which makes managing asynchronous operations a bit of a dance! Promises or async/await have got us covered, gracefully managing any Singleton high-drama.

Our IIFE pattern inherently provides lazy instantiation making sure there's no rush, and the single instance is created only when it's required, saving valuable resources. Nice, isn't it?

The 'One isn't always fun': Understanding the singleton pattern’s potential downside

The singleton pattern can be a sweet escape, providing easy access to shared resources. This might, however, lead to the pattern's misuse, turning it into an anti-pattern. Overuse can result in a bottleneck for shared resources and a potential nightmare for unit testing as maintaining the state becomes challenging!

Keep these pitfalls in mind when dealing with singletons and tread wisely!