Simplest/Cleanest way to implement singleton in JavaScript?

后端 未结 30 1080
名媛妹妹
名媛妹妹 2020-11-22 05:17

What is the simplest/cleanest way to implement singleton pattern in JavaScript?

相关标签:
30条回答
  • 2020-11-22 05:22

    In ES6 the right way to do this is:

    class MyClass {
      constructor() {
        if (MyClass._instance) {
          throw new Error("Singleton classes can't be instantiated more than once.")
        }
        MyClass._instance = this;
    
        // ... your rest of the constructor code goes after this
      }
    }
    
    var instanceOne = new MyClass() // Executes succesfully 
    var instanceTwo = new MyClass() // Throws error

    Or, if you don't want an error to be thrown on second instance creation, you can just return the last instance, like so:

    class MyClass {
      constructor() {
        if (MyClass._instance) {
          return MyClass._instance
        }
        MyClass._instance = this;
    
        // ... your rest of the constructor code goes after this
      }
    }
    
    var instanceOne = new MyClass()
    var instanceTwo = new MyClass()
    
    console.log(instanceOne === instanceTwo) // logs "true"

    0 讨论(0)
  • 2020-11-22 05:23

    For me the cleanest way to do so is :

    const singleton = new class {
        name = "foo"
        constructor() {
            console.log(`Singleton ${this.name} constructed`)
        }
    }
    

    With this syntax you are certain your singleton is and will remain unique. You can also enjoy the sugarness of class syntax and use this as expected.

    (Note that class fields require node v12+ or a modern browser.)

    0 讨论(0)
  • 2020-11-22 05:24

    I've found the following to be the easiest Singleton pattern, because using the new operator makes this immediately available within the function, eliminating the need to return an object literal:

    var singleton = new (function () {
    
      var private = "A private value";
      
      this.printSomething = function() {
          console.log(private);
      }
    })();
    
    singleton.printSomething();

    0 讨论(0)
  • 2020-11-22 05:24

    Isn't this a singleton too?

    function Singleton() {
        var i = 0;
        var self = this;
    
        this.doStuff = function () {
            i = i + 1;
            console.log( 'do stuff',i );
        };
    
        Singleton = function () { return self };
        return this;
    }
    
    s = Singleton();
    s.doStuff();
    
    0 讨论(0)
  • 2020-11-22 05:26

    Short answer:

    Because non-blocking nature of JavaScript, Singletons in JavaScript are really ugly in use. Global variables will give you one instance through whole application too without all these callbacks, module pattern gently hides internals behind the interface. See @CMS answer.

    But, since you wanted a singleton…

    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);
        }
      };
    
    };
    

    Usage:

    var singletonInitializer = function(instanceReady) {
      var preparedObject = {property: 'value'};
      // calling instanceReady notifies singleton that instance is ready to use
      instanceReady(preparedObject);
    }
    var s = singleton(singletonInitializer);
    
    // get instance and use it
    s(function(instance) {
      instance.doSomething();
    });
    

    Explanation:

    Singletons give you more than just one instance through whole application: their initialization is delayed till first use. This is really big thing when you deal with objects whose initialization is expensive. Expensive usually means I/O and in JavaScript I/O always mean callbacks.

    Don't trust answers which give you interface like instance = singleton.getInstance(), they all miss the point.

    If they don't take callback to be run when instance is ready, then they won't work when initializer does I/O.

    Yeah, callbacks always look uglier than function call which immediately returns object instance. But again: when you do I/O, callbacks are obligatory. If you don't want to do any I/O, then instantiation is cheap enough to do it at program start.

    Example 1, cheap initializer:

    var simpleInitializer = function(instanceReady) {
      console.log("Initializer started");
      instanceReady({property: "initial value"});
    }
    
    var simple = singleton(simpleInitializer);
    
    console.log("Tests started. Singleton instance should not be initalized yet.");
    
    simple(function(inst) {
      console.log("Access 1");
      console.log("Current property value: " + inst.property);
      console.log("Let's reassign this property");
      inst.property = "new value";
    });
    simple(function(inst) {
      console.log("Access 2");
      console.log("Current property value: " + inst.property);
    });
    

    Example 2, initialization with I/O:

    In this example setTimeout fakes some expensive I/O operation. This illustrates why singletons in JavaScript really need callbacks.

    var heavyInitializer = function(instanceReady) {
      console.log("Initializer started");
      var onTimeout = function() {
        console.log("Initializer did his heavy work");
        instanceReady({property: "initial value"});
      };
      setTimeout(onTimeout, 500);
    };
    
    var heavy = singleton(heavyInitializer);
    
    console.log("In this example we will be trying");
    console.log("to access singleton twice before it finishes initialization.");
    
    heavy(function(inst) {
      console.log("Access 1");
      console.log("Current property value: " + inst.property);
      console.log("Let's reassign this property");
      inst.property = "new value";
    });
    
    heavy(function(inst) {
      console.log("Access 2. You can see callbacks order is preserved.");
      console.log("Current property value: " + inst.property);
    });
    
    console.log("We made it to the end of the file. Instance is not ready yet.");
    
    0 讨论(0)
  • 2020-11-22 05:26

    Not sure why nobody brought this up, but you could just do:

    var singleton = new (function() {
      var bar = 123
    
      this.foo = function() {
        // whatever
      }
    })()
    
    0 讨论(0)
提交回复
热议问题