Singleton in JavaScript

So far we covered why we need to learn the design patterns and also explored the Singleton design pattern. Though the implementations for Java, TypeScript and even for PHP remains almost similar, the case of JavaScript is different.

JS is Quirky

Technically whenever we create any object using the object literal in JavaScript it’s singleton! No object is same until it’s the same object reference we are trying to comparing.

1
2
3
4
5
6
7
8
9
10
11
12
var userA = {
name: "Ashok",
email: "[email protected]"
};

var userB = {
name: "Ashok",
email: "[email protected]"
};

// comparison will return false
userA == UserB // false

Also there’s no class in JS (until ECMA Script 6), so we will be kind-a forcing the traditional class based singleton pattern via JS. But the catch is that it has the keyword new.

1
2
3
4
5
var loggerA = new Logger() 
var loggerB = new Logger()

// our challenge it to make the below statement true
console.log(loggerA == loggerB) == true

To accomplish that we need to cover a few things in JS:

  • IIFE (Immediate Invocation of Function Expressions)
  • Namespace
  • Closures

IIFE

IIFEs are basically functions which are crated and invoked immediately. We use IFFE for containerization of custom code to avoid polluting the global scope.

1
2
3
(function() {
// Code to be executed immediately
})();

Namespace

We create namespace to avoid conflicts and define the scope of our code. Ideally we achieve namespaces using Object literals.

1
var App = App || {};

An IIFE can be used to create a namespace, effectively preventing global scope pollution and organizing code. This pattern allows you to encapsulate variables and functions within their own scope, making them accessible only within that specific namespace.

1
2
3
4
5
6
7
8
9
10
11
12
13
var MyModule = (function() {
var count = 0 ;

return {
increment:function() {
count = count + 1;
return count;
},
reset:function() {
return count = 0;
}
}
}());

Closures

The traditional closure definitions is:

A closure is a function that retains access to its outer function’s variables, even after the outer function has finished executing.

1
2
3
4
5
6
7
8
9
10
11
12
var multiplier = function (factor) {
return function multiply(val) {
return val * factor;
}
}

// creating multiple functions using `multiplier`
var multiplyBy2 = multiplier(2);
var multiplyBy5 = multiplier(5);

console.log(multiplyBy2(10)); // 20
console.log(multiplyBy5(10)); // 50

Another benefit of closures is that a closure allow a function to keep variables hidden and only accessible within that function. This is what we will be utilizing for the singleton implementation.

Note: Closures comes with some caveats. Most common are the Memory Leaks as closures may retain unnecessary references to variables avoiding garbage cleaning.

Implementing a Singleton

Now that we have covered the required basics, let’s start with implementation of the singleton Config class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Logger = (function () {
var instance;

// closure function `Log`
function Log(level) {
if (instance) {
return instance;
}
instance = this;
Object.freeze(instance); // we should freeze this instance to avoid modifications

return instance;
}

return Log;
})();

Now if we try and run the code below it should yield true.

1
2
3
4
var loggerA = new Logger('debug');
var loggerB = new Logger('warn');

console.log(loggerA === loggerB); // true

Conclusion

I hope you find this helpful. Going though this also made me realize that learning JavaScript is one thing but mastering the techniques and using them for our own use cases will ony come through rigorous practice.