453 Stimmen

Einfachste / sauberste Möglichkeit, ein Singleton in JavaScript zu implementieren

Was ist der einfachste/sauberste Weg, um das Singleton-Muster in JavaScript zu implementieren?

34 Stimmen

Downvote dafür, dass die akzeptierte Antwort überhaupt kein Singleton ist. Es ist einfach eine globale Variable.

7 Stimmen

Dies ist eine Menge Informationen, aber es legt wirklich die Unterschiede zwischen den verschiedenen JS-Designpatterns dar. Es hat mir sehr geholfen: addyosmani.com/resources/essentialjsdesignpatterns/book

7voto

Stoyan Punkte 357

Es gibt mehr als einen Weg, um ein Problem zu lösen :) Je nach Geschmack oder spezifischer Anforderung können Sie eine der vorgeschlagenen Lösungen anwenden. Ich persönlich bevorzuge Christians C. Salvadós erste Lösung, wann immer möglich (wenn Sie keine Privatsphäre benötigen).

Da die Frage nach der einfachsten und saubersten Lösung gestellt wurde, ist das der Gewinner. Oder sogar:

var myInstance = {}; // Fertig!

Dies (Zitat von meinem Blog)...

var SingletonClass = new function() {
    this.myFunction() {
        // Mach etwas
    }
    this.instance = 1;
}

macht nicht viel Sinn (mein Blog-Beispiel auch nicht), weil es keine privaten Variablen benötigt, also ist es ziemlich das gleiche wie:

var SingletonClass = {
    myFunction: function () {
        // Mach etwas
    },
    instance: 1
}

0 Stimmen

Code-Schnipsel 2 enthält einen Syntaxfehler. Du kannst nicht this.f(){} schreiben.

7voto

skalee Punkte 11472

Ich widerrufe meine Antwort, siehe meine andere.

Normalerweise ist das Modulmuster (siehe Christian C. Salvadós Antwort), welches nicht das Singleton-Muster ist, in der Regel ausreichend. Eines der Merkmale des Singletons ist jedoch, dass seine Initialisierung aufgeschoben wird, bis das Objekt benötigt wird. Das Modulmuster hat dieses Merkmal nicht.

Mein Vorschlag (CoffeeScript):

window.singleton = (initializer) ->
  instance = undefined
  () ->
    return instance unless instance is undefined
    instance = initializer()

Das kompiliert im JavaScript zu folgendem:

window.singleton = function(initializer) {
    var instance;
    instance = void 0;
    return function() {
        if (instance !== void 0) {
            return instance;
        }
        return instance = initializer();
    };
};

Dann kann ich Folgendes tun:

window.iAmSingleton = singleton(function() {
    /* Diese Funktion sollte das Singleton erstellen und initialisieren. */
    alert("erstellen");
    return {property1: 'Wert1', property2: 'Wert2'};
});

alert(window.iAmSingleton().property2); // "erstellen" wird angezeigt; dann wird "Wert2" angezeigt
alert(window.iAmSingleton().property2); // "Wert2" wird angezeigt, aber "erstellen" wird nicht
window.iAmSingleton().property2 = 'neuer Wert';
alert(window.iAmSingleton().property2); // "neuer Wert" wird angezeigt

0 Stimmen

Warum würdest du das Modul laden, wenn es nicht benötigt wird? Und wenn du das Modul laden musst, dann lädst du das Modul und es wird initialisiert.

6voto

skalee Punkte 11472

Kurze Antwort:

Aufgrund der blockierenden Natur von JavaScript sind Singletons in JavaScript wirklich hässlich in der Anwendung. Globale Variablen geben Ihnen auch eine Instanz für die gesamte Anwendung, ohne all diese Callbacks, und das Modulmuster verbirgt sanft Interna hinter der Schnittstelle. Siehe Christian C. Salvadós Antwort.

Aber, da Sie einen Singleton wollten...

var singleton = function(initializer) {

  var state = 'initial';
  var instance;
  var queue = [];

  var instanceReady = function(createdInstance) {
    state = 'ready';
    instance = createdInstance;
    while (callback = queue.shift()) {
      callback(instance);
    }
  };

  return function(callback) {
    if (state === 'initial') {
      state = 'waiting';
      queue.push(callback);
      initializer(instanceReady);
    } else if (state === 'waiting') {
      queue.push(callback);
    } else {
      callback(instance);
    }
  };

};

Verwendung:

var singletonInitializer = function(instanceReady) {
  var preparedObject = {property: 'value'};
  // Der Aufruf von instanceReady benachrichtigt den Singleton, dass die Instanz einsatzbereit ist
  instanceReady(preparedObject);
}
var s = singleton(singletonInitializer);

// Holen Sie sich die Instanz und verwenden Sie sie
s(function(instance) {
  instance.doSomething();
});

Erklärung:

Singletons geben Ihnen mehr als nur eine Instanz für die gesamte Anwendung: Ihre Initialisierung wird bis zur ersten Verwendung verzögert. Dies ist wirklich wichtig, wenn Sie es mit Objekten zu tun haben, deren Initialisierung teuer ist. Teuer bedeutet in der Regel I/O und in JavaScript bedeutet I/O immer Callbacks.

Vertrauen Sie nicht auf Antworten, die Ihnen eine Schnittstelle wie instance = singleton.getInstance() geben, sie verfehlen alle den Punkt.

Wenn sie keinen Callback akzeptieren, der ausgeführt wird, wenn eine Instanz einsatzbereit ist, funktionieren sie nicht, wenn der Initialisierer I/O durchführt.

Ja, Callbacks sehen immer hässlicher aus als ein Funktionsaufruf, der sofort eine Objektinstanz zurückgibt. Aber noch einmal: Wenn Sie I/O durchführen, sind Callbacks obligatorisch. Wenn Sie keine I/O machen möchten, ist die Instantiierung billig genug, um sie zu Programmstart durchzuführen.

Beispiel 1, günstiger Initialisierer:

var simpleInitializer = function(instanceReady) {
  console.log("Initialisierer gestartet");
  instanceReady({property: "Anfangswert"});
}

var simple = singleton(simpleInitializer);

console.log("Tests gestartet. Singleton-Instanz sollte noch nicht initialisiert sein.");

simple(function(inst) {
  console.log("Zugriff 1");
  console.log("Aktueller Eigenschaftswert: " + inst.property);
  console.log("Lassen Sie uns diese Eigenschaft neu zuweisen");
  inst.property = "neuer Wert";
});
simple(function(inst) {
  console.log("Zugriff 2");
  console.log("Aktueller Eigenschaftswert: " + inst.property);
});

Beispiel 2, Initialisierung mit I/O:

In diesem Beispiel simuliert setTimeout eine teure I/O-Operation. Dies verdeutlicht, warum Singletons in JavaScript wirklich Callbacks benötigen.

var heavyInitializer = function(instanceReady) {
  console.log("Initialisierer gestartet");
  var onTimeout = function() {
    console.log("Initialisierer hat seine schwere Arbeit gemacht");
    instanceReady({property: "Anfangswert"});
  };
  setTimeout(onTimeout, 500);
};

var heavy = singleton(heavyInitializer);

console.log("In diesem Beispiel werden wir versuchen,");
console.log("auf den Singleton zweimal zuzugreifen, bevor die Initialisierung abgeschlossen ist.");

heavy(function(inst) {
  console.log("Zugriff 1");
  console.log("Aktueller Eigenschaftswert: " + inst.property);
  console.log("Lassen Sie uns diese Eigenschaft neu zuweisen");
  inst.property = "neuer Wert";
});

heavy(function(inst) {
  console.log("Zugriff 2. Sie können sehen, dass die Callback-Reihenfolge erhalten bleibt.");
  console.log("Aktueller Eigenschaftswert: " + inst.property);
});

console.log("Wir haben es bis zum Ende der Datei geschafft. Die Instanz ist noch nicht bereit.");

0 Stimmen

Durch Prüfungen und Schwierigkeiten mit anderen einzelnen Antworten, die nicht ausreichten, landete ich schließlich auf einem Ergebniscode, der bemerkenswert ähnlich zu diesem war.

0 Stimmen

Aus einem Grund oder einem anderen ist dies die einzige Antwort, die für mich Sinn macht. Die anderen Antworten erinnern mich alle an die Folge der Goon Show, in der drei Männer versuchen, eine vier Personen hohe Wand zu erklimmen, indem sie sich gegenseitig auf den Schultern klettern.

0 Stimmen

Das Calback-Stacking ist genau das, was ich gebraucht habe! Danke!!

6voto

David Punkte 1148

Ich denke, ich habe den saubersten Weg gefunden, um in JavaScript zu programmieren, aber du wirst etwas Vorstellungskraft brauchen. Ich habe diese Idee aus einer funktionierenden Technik im Buch _JavaScript: The Good Parts_.

Anstatt das new Schlüsselwort zu verwenden, könntest du eine Klasse so erstellen:

function Class()
{
    var obj = {}; // Könnte auch für Vererbung verwendet werden, wenn du nicht mit einem leeren Objekt beginnst.

    var privateVar;
    obj.publicVar;

    obj.publicMethod = publicMethod;
    function publicMethod(){}

    function privateMethod(){}

    return obj;
}

Du kannst das obenstehende Objekt instanziieren, indem du sagst:

var objInst = Klasse(); // !!! KEIN NEW-SCHLÜSSELWORT

Jetzt, mit dieser Arbeitsmethode im Hinterkopf, könntest du einen Singleton so erstellen:

ClassSingleton = function()
{
    var instance = null;

    function Class() // Das ist die Klasse wie die obige
    {
        var obj = {};
        return obj;
    }

    function getInstance()
    {
        if( !instance )
            instance = Class(); // Wieder kein 'new'-Schlüsselwort;

        return instance;
    }

    return { getInstance : getInstance };
}();

Jetzt kannst du deine Instanz bekommen, indem du folgendes aufrufst:

var obj = ClassSingleton.getInstance();

Ich denke, das ist der sauberste Weg, da die komplette "Klasse" nicht einmal zugänglich ist.

1 Stimmen

Aber mit dieser Technik könnten Sie mehr als eine Instanz haben. Das ist nicht richtig.

1 Stimmen

Ich denke nicht, dass du auch ohne über getInstance zugreifen kannst. Könntest du das genauer erklären?

0 Stimmen

David Maes Entschuldigung, aber ich habe die Validierung im zweiten Beispiel nicht bemerkt. Es tut mir leid.

5voto

令狐葱 Punkte 1037

Die klarste Antwort sollte diese aus dem Buch Learning JavaScript Design Patterns von Addy Osmani sein.

var mySingleton = (function () {

  // Instanz speichert eine Referenz auf das Singleton
  var instance;

  function init() {

    // Singleton

    // Private Methoden und Variablen
    function privateMethod(){
        console.log( "Ich bin privat" );
    }

    var privateVariable = "Ich bin auch privat";

    var privateRandomNumber = Math.random();

    return {

      // Öffentliche Methoden und Variablen
      publicMethod: function () {
        console.log( "Die Öffentlichkeit kann mich sehen!" );
      },

      publicProperty: "Ich bin auch öffentlich",

      getRandomNumber: function() {
        return privateRandomNumber;
      }

    };

  };

  return {

    // Gib die Singleton-Instanz zurück, wenn eine existiert
    // oder erstelle eine neue, falls keine vorhanden ist
    getInstance: function () {

      if ( !instance ) {
        instance = init();
      }

      return instance;
    }

  };

})();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X