用JavaScript实现单例模式的最简单/最干净的方法是什么?


当前回答

下面是我实现单例模式的演练片段。这是我在面试过程中想到的,我觉得我应该在某个地方捕捉到这一点。

/*************************************************
 *     SINGLETON PATTERN IMPLEMENTATION          *
 *************************************************/

// Since there aren't any classes in JavaScript, every object
// is technically a singleton if you don't inherit from it
// or copy from it.
var single = {};


// Singleton Implementations
//
// Declaring as a global object...you are being judged!

var Logger = function() {
  // global_log is/will be defined in the GLOBAL scope here
  if(typeof global_log === 'undefined'){
    global_log = this;
  }
  return global_log;
};


// The below 'fix' solves the GLOABL variable problem, but
// the log_instance is publicly available and thus can be
// tampered with.
function Logger() {
  if(typeof Logger.log_instance === 'undefined') {
    Logger.log_instance = this;
  }

  return Logger.log_instance;
};


// The correct way to do it to give it a closure!

function logFactory() {
  var log_instance; // Private instance
  var _initLog = function() { // Private init method
    log_instance = 'initialized';
    console.log("logger initialized!")
  }
  return {
    getLog : function(){ // The 'privileged' method
      if(typeof log_instance === 'undefined') {
        _initLog();
      }
      return log_instance;
    }
  };
}


/***** TEST CODE ************************************************

// Using the Logger singleton
var logger = logFactory(); // Did I just give LogFactory a closure?

// Create an instance of the logger
var a = logger.getLog();

// Do some work
// Get another instance of the logger
var b = logger.getLog();

// Check if the two logger instances are same
console.log(a === b); // true
*******************************************************************/

同样可以在我的主旨页面上找到。

其他回答

我相信这是最简单/最干净和最直观的方式,尽管它需要ECMAScript 2016 (ES7):

导出默认类Singleton { 静态实例; 构造函数(){ 如果(){ 返回实例; } 这一点。State = "公爵"; 这一点。实例= this; } }

源代码来自:adam-bien.com

我从JavaScript模式中得到了这个例子 用编码和设计模式构建更好的应用程序一书(Stoyan Stefanov著)。如果你需要一些简单的实现类,比如单例对象,你可以使用一个直接函数,如下所示:

var ClassName;

(function() {
    var instance;
    ClassName = function ClassName() {
        // If the private instance variable is already initialized, return a reference
        if(instance) {
            return instance;
        }
        // If the instance is not created, save a pointer of the original reference
        // to the private instance variable.
        instance = this;

        // All constructor initialization will be here
        // i.e.:
        this.someProperty = 0;
        this.someMethod = function() {
            // Some action here
        };
    };
}());

你可以通过下面的测试用例来检查这个例子:

// Extending defined class like singleton object using the new prototype property
ClassName.prototype.nothing = true;
var obj_1 = new ClassName();

// Extending the defined class like a singleton object using the new prototype property
ClassName.prototype.everything = true;
var obj_2 = new ClassName();

// Testing makes these two objects point to the same instance
console.log(obj_1 === obj_2); // Result is true, and it points to the same instance object

// All prototype properties work
// no matter when they were defined
console.log(obj_1.nothing && obj_1.everything
            && obj_2.nothing && obj_2.everything); // Result true

// Values of properties which are defined inside of the constructor
console.log(obj_1.someProperty); // Outputs 0
console.log(obj_2.someProperty); // Outputs 0

// Changing property value
obj_1.someProperty = 1;

console.log(obj_1.someProperty); // Output 1
console.log(obj_2.someProperty); // Output 1

console.log(obj_1.constructor === ClassName); // Output true

这种方法通过了所有测试用例,而当使用原型扩展时,私有静态实现将失败(它可以被修复,但并不简单),而公共静态实现则不太可取,因为实例是公开给公众的。

jsFiddly demo.

我认为最简单的方法是声明一个简单的对象文字:

var myInstance = {
  method1: function () {
    // ...
  },
  method2: function () {
    // ...
  }
};

如果你想在你的单例实例上有私有成员,你可以这样做:

var myInstance = (function() {
  var privateVar = '';

  function privateMethod () {
    // ...
  }

  return { // public interface
    publicMethod1: function () {
      // All private members are accessible here
    },
    publicMethod2: function () {
    }
  };
})();

这被称为模块模式,它基本上允许您通过使用闭包来封装对象上的私有成员。

如果你想阻止对单例对象的修改,你可以使用ES5 object .freeze方法冻结它。

这将使对象不可变,防止对其结构和值进行任何修改。

如果你正在使用ES6,你可以很容易地使用ES模块来表示一个单例,你甚至可以通过在模块范围内声明变量来保持私有状态:

// my-singleton.js
const somePrivateState = []

function privateFn () {
  // ...
}

export default {
  method1() {
    // ...
  },
  method2() {
    // ...
  }
}

然后你可以简单地导入单例对象来使用它:

import myInstance from './my-singleton.js'
// ...

对我来说,最干净的方法是:

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

使用这种语法,您可以确定您的单例是并且将保持惟一的。您还可以享受类语法的甜蜜,并按预期使用它。

(注意,类字段需要节点v12+或现代浏览器。)

我认为最干净的方法是:

var SingletonFactory = (function(){
    function SingletonClass() {
        //do stuff
    }
    var instance;
    return {
        getInstance: function(){
            if (instance == null) {
                instance = new SingletonClass();
                // Hide the constructor so the returned object can't be new'd...
                instance.constructor = null;
            }
            return instance;
        }
   };
})();

之后,可以调用函数as

var test = SingletonFactory.getInstance();