Back in 1994, a book was authored by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides that discusses 23 desgin patterns, titled Design Patterns: Elements of Resuable Object-Oriented Software. You may have heard of this book or the authors as Gang of Four (GoF).
单例模式(Singleton Pattern)是最简单的设计模式之一。这种类型的设计模式属于建立型 (Creational) 模式,它提供了一种建立对象的最佳方式。javascript
这种模式涉及到一个单一的类,该类负责建立本身的对象,同时确保只有单个对象被建立。这个类提供了一种访问其惟一的对象的方式,能够直接访问,不须要实例化该类的对象。html
注意:java
单例模式是一种经常使用的模式,有一些对象咱们每每只须要一个,好比线程池、全局缓存、浏览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途一样很是普遍。试想一下,当咱们单击登陆按钮的时候,页面中会出现一个登陆浮窗,而这个登陆浮窗是惟一的,不管单击多少次登陆按钮,这个浮窗都只会被建立一次,那么这个登陆浮窗就适合用单例模式来建立。git
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。es6
主要解决:一个全局使用的类频繁地建立与销毁。github
什么时候使用:当您想控制实例数目,节省系统资源的时候。typescript
如何解决:判断系统是否已经有这个单例,若是有则返回,若是没有则建立。设计模式
关键代码:构造函数是私有的。浏览器
咱们将建立一个 SingleObject 类。SingleObject 类有它的私有构造函数和自己的一个静态实例。缓存
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,咱们的演示类使用 SingleObject 类来获取 SingleObject 对象。
要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类建立过对象,若是是,则在下一次获取该类的实例时,直接返回以前建立的对象。代码以下:
var Singleton = function(name) { this.name = name } Singleton.prototype.getName = function() { alert(this.name) } Singleton.getInstance = (function() { var instance = null return function(name) { if (!instance) { instance = new Singleton(name) } return instance } })() var a = Singleton.getInstance( 'sven1' ); var b = Singleton.getInstance( 'sven2' ); alert ( a === b ); // true
咱们经过 Singleton.getInstance 来获取 Singleton 类的惟一对象,这种方式相对简单,但有 一个问题,就是增长了这个类的“不透明性”,Singleton 类的使用者必须知道这是一个单例类, 跟以往经过 new XXX 的方式来获取对象不一样,这里偏要使 Singleton.getInstance 来获取对象。
上面单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从 “类” 中建立而来。在以类为中心的语言中,这是很天然的作法。好比在 Java 中,若是须要某个对象,就必须先定义一个类,对象老是从类中建立而来的。
但 JavaScript 实际上是一门无类(class-free)语言,也正由于如此,生搬单例模式的概念并没有意义。在 JavaScript 中建立对象的方法很是简单,既然咱们只须要一个“惟一”的对象,为什 么要为它先建立一个“类” 呢?这无异于穿棉衣洗澡,传统的单例模式实如今 JavaScript 中并 不适用。
1.使用命名空间
适当地使用命名空间,并不会杜绝全局变量,但能够减小全局变量的数量。 最简单的方法依然是用对象字面量的方式:
var namespace1 = { a: function() { alert(1) }, b: function() { alert(2) }, }
2.使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通讯:
var namespace = { getSingleton: (function() { // BEGIN iife var singleton return function() { if (!singleton) { singleton = { amethod: function() { console.log('amethod') }, } } return singleton } })(), // END iife } // Invoke: namespace.getSingleton().amethod()
ES6 里有了模块和类的概念,实现起来会变得不同:
const singleton = Symbol(); const singletonEnforcer = Symbol(); class SingletonEnforcer { constructor(enforcer) { if (enforcer !== singletonEnforcer) { throw new Error('Cannot construct singleton'); } this._type = 'SingletonEnforcer'; } static get instance() { if (!this[singleton]) { this[singleton] = new SingletonEnforcer(singletonEnforcer); } return this[singleton]; } singletonMethod() { return 'singletonMethod'; } static staticMethod() { return 'staticMethod'; } get type() { return this._type; } set type(value) { this._type = value; } } export default SingletonEnforcer;
TypeScript 中有了 private
等概念,实现起来会更加有趣。
让咱们想象一下,咱们想要一个跟踪温度的类。在这个系统中,咱们但愿有一个入口,能够改变温度。这就是 Singleton类 的样子:
class Singleton { private static instance: Singleton; private _temperature: number; private constructor() { } static getInstance() { if (!Singleton.instance) { Singleton.instance = new Singleton(); Singleton.instance._temperature = 0; } return Singleton.instance; } get temperature(): number { return this._temperature; } set temperature(score) { this._temperature = score; } increaseTemperature(): number { return this._temperature += 1; } decreaseTemperature(): number { return this._temperature -= 1; } } const myInstance = Singleton.getInstance(); console.log(myInstance.temperature); // 0
上面的实现有一下几点须要注意:
若是使用 new 关键字建立对象:
const myInstance = new Singleton(); // Constructor of class 'Singleton' is private and only accessible within the class declaration.
根据上面的例子,咱们也能够访问 temperature 属性。如今让咱们设置温度值并将其增长/减小几回:
console.log(myInstance.temperature = 25); // 25 console.log(myInstance.increaseTemperature()); // 26 console.log(myInstance.increaseTemperature()); // 27 console.log(myInstance.decreaseTemperature()); // 26
在 java 中,单例模式根据是否 lazy loading (懒汉模式/饿汉模式)以及是否线程安全,分为不少种实现方式。而在 JS 中,咱们不用关注线程安全的问题,所以不管是代码复杂度仍是实现难度都会低不少。