Pascal Precht

Singleton in JavaScript

Leute die bereits wissen was Entwurfsmuster – oder auch Design Patterns – sind, kennen in der Regel das sogenannte Singleton Pattern. Christoph Witting hat hier bereits einen entsprechenden Artikel für .NET-Entwickler geschrieben. Die Idee des Singleton Patterns besteht also darin, nur eine Instanz einer bestimmten Klasse zu haben.
Wenn also ein zweites Objekt dieser Klasse erzeugt wird, handelt es sich um die exakt gleiche Instanz wie die der Ersten. Werfen wir einen Blick auf JavaScript, stellen wir schnell fest, dass es keine Klassen gibt. Wie also soll ein Muster dieser Art in JavaScript implementiert werden?!

In JavaScript sind Objekte niemals gleich

Es klingt ein bisschen verwirrend, aber es ist nunmal so. Wenn wir ein neues Objekt in JavaScript erstellen, gibt es kein anderes Objekt, das gleich ist, und das neue Objekt ist schon ein Singleton. Schauen wir uns folgendes Codebeispiel an, lässt sich schnell erkennen, das die Erzeugung eines Objektes mit Objekt-Literalen ein Singleton ins Leben ruft.

var myObj = {
    myAttribute: 'JS is awesome!'
};

var myObj2 = {
    myAttribute: 'JS is awesome!'
};

myObj === myObj2    // false
myObj == myObj2    // false

Also grundsätzlich gilt: Immer dann, wenn ein Objekt mit Objekt-Literalen erzeugt wird, erstellt man eigentlich ein Singleton.

Singleton mit new-Konstruktor

Eine weitere Möglichkeit ein Singleton zu erzeugen ist die mit der Verwendung von new. Es gibt Fälle, in denen ist es gewollt ein Objekt immer mit dem new-Operator zu instanziieren. Auch hier gilt: Bei jeder Erzeugung einer solchen Instanz wird immer auf das gleiche Objekt gezeigt. So könnte also die Schnittstelle aussehen, wenn man das Singleton-Pattern mit der Verwendung von new implementiert:

var numberOne = new Singleton();
var numberTwo = new Singleton();

numberOne === numberTwo;  // true

Letztendlich wird also nur bei numberOne die eigentliche Instanz erzeugt. Jeder weitere Aufruf von “new Singleton();” erzeugt nichts weiter als eine Referenz auf die bereits bestehende Instanz. Wie lässt sich das Ganze aber nun umsetzen?

Grundsätzlich gibt es mehrere Möglichkeiten das obige Ziel zu erreichen. Die stumpfste, die mir als erstes in den Sinn kommt, wäre die Verwendung einer globalen Variable, in der eine entsprechende Instanz gespeichert wird. Das das nicht sonderlich vom Vorteil ist, wissen wir. Der globale Namensraum sollte freigehalten werden. Zudem könnte unter diesen Umständen der Wert unserer globalen Variable jeder Zeit von außen geändert werden.

Weg Nummer zwei wäre der Konstruktor-Funktion für Singleton eine statische Eigenschaft hinzuzufügen, welche die Instanz hält. Da Funktionen in JavaScript auch nur Objekte sind, ist das kein Problem. Ein Ansatz wie Singleton.__instance wäre also denkbar. Nur gibt’s auch hier wie immer einen Haken. Statische Eigenschaften eines Objektes in dieser Form sind ebenfalls global öffentlich. Das wiederum bedeutet, das auch in diesem Fall der Wert der Eigenschaft einfach von außen geändert werden kann.

Zu guter Letzt wäre es möglich, ein Closure zu erzeugen, wodurch wir eine mögliche Eigenschaft __instance in eine Art gefaketen privaten Scope holen.

Gehn wir’s an!

function Singleton () {

    if (typeof Singleton.__instance === "object") {
        return Singleton.__instance;
    }

    Singleton.__instance = this;
}

var numberOne = new Singleton();
var numberTwo = new Singleton();

numberOne === numberTwo; // true

Hier sehen wir die Lösung mit der Instanz als statische Eigenschaft. Unwahrscheinlich, aber nicht unmöglich ist hier, __instance von außen einfach zu ändern.
Schauen wir uns nun einen möglichen Lösungsweg mit der Instanz in einem Closure an.

function Singleton() {

    var __instance = this;

    Singleton = function () {
        return __instance;
    }
}

var numberOne = new Singleton();
var numberTwo = new Singleton();

numberOne === numberTwo; // true

Hier wird also beim ersten Aufruf ganz normal this zurück gegeben. Bei jedem weiteren Aufruf wird die mit bereits this versehende Variable __instance zurück gegeben, da sich der Konstruktor “Singleton” selbst definiert. Der Konstruktor wird also beim ersten Aufruf überschrieben, hierbei handelt es sich übrigens um eine “Lazy Function definition”.
Allerdings gibt es hier den Nachteil, dass die neugeschriebene Funktion alle Eigenschaften verliert, die zwischen der initialen Definition und der Redefinition hinzugefügt wurden.
Hier mal ein konkreter Fall:

Singleton.prototype.something = false;

var singleton = new Singleton();

Singleton.prototype.somethingelse = true;

var singleton2 = new Singleton();

// Und nun der Test
singleton.something; //false
singleton2.something; // false

singleton.somethingelse; // undefined
singleton2.somethingelse; // undefined

singleton.constructor.name; // "Singleton"

singleton.constructor === Singleton; // false

Durch die Redefinition zeigt singleton.constructor nicht mehr auf den Konstruktor Singleton(). Um das zu fixen kann man ein paar Kniffe verwenden.

function Singleton() {

    var __instance;

    Singleton = function Singleton() {
        return __instance;
    }

    Singleton.prototype = this;

    __instance = new Singleton();
    __instance.constructor = Singleton;

    return __instance;
}

// Und nochmal testen
Singleton.prototype.something = false;
var singleton = new Singleton();

Singleton.prototype.somethingelse = false;
var singleton2 = new Singleton();

singleton === singleton2; // true
singleton.constructor === Singleton; // true

Perfekt! Fertig ist unser Singleton! Zur Verschönerung packen wir alles nochmal in eine Immediate Function. So wird beim ersten Aufruf des Konstruktors ein Objekt erzeugt und der “privaten” Variable __instance zugewiesen. Bei jedem weiteren Aufruf wird einfach diese zurück gegeben. Somit haben wir ein kleines Modul geschaffen, welches uns unser Singleton erzeugt.

var Singleton;

(function () {
    var __instance;

    Singleton = function Singleton() {

        if (__instance) {
            return __instance;
        }
    };
}());