Singleton Pattern in JavaScript: A Complete Guide
The Singleton Pattern is a fundamental design pattern that restricts the instantiation of a class to a single object, ensuring a single point of access to that instance throughout an application. This article explains the core concepts of the Singleton Pattern, its common use cases, its advantages and drawbacks, and demonstrates multiple practical approaches to implementing it in JavaScript using modern ES6 syntax and module patterns.
What is the Singleton Pattern?
The Singleton Pattern is a creational design pattern. It solves two primary problems:
- Ensures a class has only one instance: It prevents multiple allocations of objects that manage shared resources.
- Provides a global access point: It allows any part of the application to access that unique instance without passing references manually.
Common real-world use cases for Singletons include: - Global configuration managers - Logging mechanisms - Database connection pools - Application state managers
Implementing the Singleton Pattern in JavaScript
JavaScript provides several ways to implement the Singleton Pattern depending on your programming paradigm and runtime environment.
1. Implementation Using ES6 Classes
In an ES6 class, you can check whether an instance already exists inside the constructor. If it does, return the existing instance; if not, create and cache it.
class DatabaseConnection {
constructor(connectionString) {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.connectionString = connectionString;
this.isConnected = true;
DatabaseConnection.instance = this;
}
query(sql) {
console.log(`Executing query "${sql}" on ${this.connectionString}`);
}
}
// Usage
const db1 = new DatabaseConnection("mongodb://localhost:27017/app");
const db2 = new DatabaseConnection("mongodb://localhost:27017/other");
console.log(db1 === db2); // true
console.log(db2.connectionString); // "mongodb://localhost:27017/app"2. Implementation Using ES Modules (Modern Standard)
In modern JavaScript (ES Modules and CommonJS), modules are cached after the first time they are loaded. Exporting a single instance of an object from a module automatically creates a Singleton.
// configManager.js
class ConfigManager {
constructor() {
this.config = {};
}
set(key, value) {
this.config[key] = value;
}
get(key) {
return this.config[key];
}
}
// Export a single instance
const configManager = new ConfigManager();
export default configManager;// app.js
import configManager from './configManager.js';
configManager.set('theme', 'dark');
// otherModule.js
import configManager from './configManager.js';
console.log(configManager.get('theme')); // "dark"3. Implementation Using Closures and IIFEs
Before ES6 classes, Immediately Invoked Function Expressions (IIFEs) and closures were the standard way to encapsulate and control access to a single instance.
const AppLogger = (function () {
let instance;
function createInstance() {
return {
log: function (message) {
console.log(`[LOG]: ${message}`);
}
};
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Usage
const logger1 = AppLogger.getInstance();
const logger2 = AppLogger.getInstance();
console.log(logger1 === logger2); // trueAdvantages and Disadvantages
Advantages
- Controlled Access: Guarantees strict control over how and when a shared resource is accessed.
- Reduced Memory Footprint: Prevents the redundant creation of heavyweight objects.
- Consistent State: Ensures all parts of the application read from and write to the same data source.
Disadvantages
- Hidden Dependencies: Using global instances can make code dependencies less explicit.
- Testing Difficulties: Singletons maintain global state between tests, making isolation and unit testing more challenging.
- Violation of Single Responsibility Principle: A Singleton often manages its own lifecycle in addition to its core business logic.