Simplest/Cleanest way to implement singleton in Ja

2018-12-31 08:48发布

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

30条回答
素衣白纱
2楼-- · 2018-12-31 09:25

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.");
查看更多
琉璃瓶的回忆
3楼-- · 2018-12-31 09:26

How about this way, just insure the class can not new again.

By this, you can use the instanceof op, also, you can use the prototype chain to inherit the class,it's a regular class, but can not new it,if yuu want to get the instance just use getInstance

function CA()
{
    if(CA.instance)
    {
        throw new Error('can not new this class');
    }else{
        CA.instance = this;
    }

}
/**
 * @protected
 * @static
 * @type {CA}
 */
CA.instance = null;
/** @static */
CA.getInstance = function()
{
    return CA.instance;
}

CA.prototype = 
/** @lends CA#*/
{
    func: function(){console.log('the func');}
}
// initilize the instance
new CA();

// test here
var c = CA.getInstance()
c.func();
console.assert(c instanceof CA)
// this will failed
var b = new CA();

If you don't want to expose the instance member, just put it into a closure.

查看更多
余欢
4楼-- · 2018-12-31 09:27

Module pattern: in "more readable style". You can see easily which methods are publics and which ones are privates

var module = (function(_name){
   /*Local Methods & Values*/
   var _local = {
      name : _name,
      flags : {
        init : false
      }
   }

   function init(){
     _local.flags.init = true;
   }

   function imaprivatemethod(){
     alert("hi im a private method");
   }

   /*Public Methods & variables*/

   var $r = {}; //this object will hold all public methods.

   $r.methdo1 = function(){
       console.log("method1 call it");
   }

   $r.method2 = function(){
      imaprivatemethod(); //calling private method
   }

   $r.init = function(){
      inti(); //making init public in case you want to init manually and not automatically
   }

   init(); //automatically calling init method

   return $r; //returning all publics methods

})("module");

now you can use publics methods like

module.method2(); //-> I'm calling a private method over a public method alert("hi im a private method")

http://jsfiddle.net/ncubica/xMwS9/

查看更多
只若初见
5楼-- · 2018-12-31 09:28

I think I have found the cleanest way to program in JavaScript, but you'll need some imagination. I got this idea from a working technique in the book "javascript the good parts".

Instead of using the new keyword, you could create a class like this:

function Class()
{
    var obj = {}; // Could also be used for inheritence if you don't start with an empty object.

    var privateVar;
    obj.publicVar;

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

    function privateMethod(){} 

    return obj;
}

You can instantiate the above object by saying:

var objInst = Class(); // !!! NO NEW KEYWORD

Now with this work method in mind you could create a singleton like this:

ClassSingleton = function()
{
    var instance= null;

    function Class() // This is the class like the above one
    {
        var obj = {};
        return obj;
    }

    function getInstance()
    {
        if( !instance )
            instance = Class(); // Again no new keyword;

        return instance;
    }   

    return { getInstance : getInstance };

}();

Now you can get your instance by calling

var obj = ClassSingleton.getInstance();

I think this is the neatest way as the complete "Class" is not even accessible.

查看更多
妖精总统
6楼-- · 2018-12-31 09:28

I believe this is the simplest/cleanest and most intuitive way though it requires ES7:

export default class Singleton {

  static instance;

  constructor(){
    if(instance){
      return instance;
    }

    this.state = "duke";
    this.instance = this;
  }

}

The source code is from: adam-bien.com

查看更多
闭嘴吧你
7楼-- · 2018-12-31 09:29

The following works in node v6

class Foo {
  constructor(msg) {

    if (Foo.singleton) {
      return Foo.singleton;
    }

    this.msg = msg;
    Foo.singleton = this;
    return Foo.singleton;
  }
}

We test:

const f = new Foo('blah');
const d = new Foo('nope');
console.log(f); // => Foo { msg: 'blah' }
console.log(d); // => Foo { msg: 'blah' }
查看更多
登录 后发表回答