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).javascript
單例模式(Singleton Pattern)是最簡單的設計模式之一。這種類型的設計模式屬於建立型 (Creational) 模式,它提供了一種建立對象的最佳方式。html
這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。java
注意:git
單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏覽器中的 window 對象等。在 JavaScript 開發中,單例模式的用途一樣很是普遍。試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸浮窗,而這個登陸浮窗是惟一的,不管單擊多少次登陸按鈕,這個浮窗都只會被建立一次,那麼這個登陸浮窗就適合用單例模式來建立。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 中,咱們不用關注線程安全的問題,所以不管是代碼複雜度仍是實現難度都會低不少。