Singletons in JavaScript
How to implement them and whether they’re actually useful
Published on
Jul 26, 2020
Read time
3 min read
Introduction
A singleton is a function or class which can have only one instance. It’s a design pattern popularised by the “Gang of Four” in their influential Design Patterns. However, the singleton is one of several patterns in the book which have been criticised. Some are regarded as anti-patterns, while others are only useful in particular languages. So, what about singletons? And do they make sense JavaScript?
In this article, we’ll see how to implement singletons in JavaScript, before asking whether or not they’re useful in the language.
Class Singleton
Since Design Patterns focused on C++, we’re used to seeing singletons as classes. Since ES6, we’ve had a class syntax in JavaScript, so a basic implementation of a singleton could look something like this:
class Singleton {
constructor() {
if (!Singleton._instance) {
Singleton._instance = this;
}
return Singleton._instance;
}
}
Function Singleton
If we need backwards-compatibility or prefer functions over classes, we can do this with very small tweaks to the example above.
function Singleton() {
if (!Singleton._instance) {
Singleton._instance = this;
}
return Singleton._instance;
}
Accessing the Instance
Whether we’re using a class or a function, any time we call new Singleton()
, we’ll be referring to the same instance. But this could easily become confusing: it’s not the expected behaviour for the new
keyword.
For that reason, it’s common to provide a getInstance
method on the class or function itself.
Class getInstance
class Singleton {
constructor() {
if (!Singleton._instance) {
Singleton._instance = this;
}
return Singleton._instance;
}
static getInstance() {
return this._instance;
}
}
Once we’ve created an instance with new Singleton()
, we can then call Singleton.getInstance()
.
Function getInstance
function Singleton() {
if (!Singleton._instance) {
Singleton._instance = this;
}
Singleton.getInstance = function () {
return this._instance;
};
return Singleton._instance;
}
Once we’ve called the function Singleton()
, we can then call Singleton.getInstance()
. Now we know how to create singletons, should we actually be using them in our code?
The Argument For
In JavaScript, there’s one main technical advantage for singleton: lazy-loading. Unlike global variables, our singleton will be instantiated only when we first need it.
Whether this is a meaningful benefit of not will depend on your specific use-case. If a singleton contains stateful values, if it is time-consuming or memory-intensive to instantiate — and if a global variable would, therefore, block other more critical code — then you might be better off with a singleton.
The Argument Against
However, for me, it’s difficult to think of practical examples in real-life projects where this technical consideration would really offer enough advantage to justify the limitations (and potential confusion) that a singleton could create.
Imagine you want to build a Logger
service that sends logs to a third-party log management tool. You need to send logs throughout the application, but you only want to authorise the third-party integration once. A singleton, a bit like the one below, might seem like a reasonable solution:
class Logger {
constructor(config) {
if (!Logger._instance) {
Logger._instance = this;
authenticateLogger(config);
}
return Logger._instance;
}
log(...logs) {
sendLogsToLogManagementTool(...logs);
console.log(...logs);
}
static getInstance() {
return this._instance;
}
}
export default Logger;
Now, whenever a developer tries to create a new Logger
, no unnecessary code will run because we’ll still be working from just one instance.
But what if a developer doesn’t realise that they’re using a singleton? When we defined our class, it might have seemed like there would be no future need to extend the Logger
function or create a second instance.
But in fact, it’s easy enough to come up with a scenario: what if we decided we wanted a specific part of the codebase to be associated with different credentials in our logging tool? Maybe different teams want to monitor different parts of the application separately.
Instead, we could simply export an instance of the Logger
class:
class Logger {
constructor(config) {
authenticateLogger(config);
}
log(...logs) {
sendLogsToLogManagementTool(...logs);
console.log(...logs);
}
}
const logger = new Logger(config);
export default logger;
With this solution:
- Our class behaves as expected, so there’s no risk of confusion or misuse,
- It’s easily extended by other developers,
- It makes unit testing of the class and individual instances easier,
- The class code gets a little less complex, as we no longer need a
getInstance
method.
The benefit of lazy-loading may be lost, but our authenticateLogger
function is unlikely to be that performance-intensive anyway.
What do you think? Is there a place for singletons in JavaScript?
Related articles
You might also enjoy...
How to Create a Super Minimal MDX Writing Experience
Learn to create a custom MDX experience so you can focus on writing without worrying about boilerplate or repetition
12 min read
I Fixed Error Handling in JavaScript
How to steal better strategies from Rust and Go—and enforce them with ESLint
14 min read
How to Easily Support ESM and CJS in Your TypeScript Library
A simple example that works for standalone npm libraries and monorepos
5 min read