JavaScript 設計模式(一):單例模式

單例模式:限制類實例化次數只能一次,一個類只有一個實例,並提供一個訪問它的全局訪問點。javascript

單例模式是建立型設計模式的一種。針對全局僅需一個對象的場景,如線程池、全局緩存、window 對象等。html

模式特色

  1. 類只有一個實例
  2. 全局可訪問該實例
  3. 自行實例化(主動實例化)
  4. 可推遲初始化,即延遲執行(與靜態類/對象的區別)

JavaScript 是一門非正規面向對象的語言,並無類的定義。而單例模式要求一個 「惟一」 和 「全局訪問」 的對象,在 JavaScript 中相似全局對象,恰好知足單例模式的兩個特色:「惟一」 和 「可全局訪問」。雖然它不是正規的單例模式,但不能否認確實具有類單例模式的特色。前端

// 全局對象
var globaObj = {};
複製代碼

使用全局變量會有如下問題:java

  1. 命名空間污染(變量名衝突)
  2. 維護時不方便管控(容易不當心覆蓋)

全局變量問題折中的應對方案:git

  1. 使用命名空間
  2. 閉包封裝私有變量(利用函數做用域)
  3. ES6的 const/symbol

雖然全局變量能夠實現單例,但因其自身的問題,不建議在實際項目中將其做爲單例模式的應用,特別是中大型項目的應用中,全局變量的維護該是考慮的成本。github

模式實現

實現方式:使用一個變量存儲類實例對象(值初始爲 null/undefined )。進行類實例化時,判斷類實例對象是否存在,存在則返回該實例,不存在則建立類實例後返回。屢次調用類生成實例方法,返回同一個實例對象。編程

「簡單版」 單例模式:

let Singleton = function(name) {
    this.name = name;
    this.instance = null;
}

Singleton.prototype.getName = function() {
    console.log(this.name);
}

Singleton.getInstance = function(name) {
    if (this.instance) {
        return this.instance;
    }
    return this.instance = new Singleton(name);
}

let Winner = Singleton.getInstance('Winner');
let Looser = Singleton.getInstance('Looser');

console.log(Winner === Looser); // true
console.log(Winner.getName());  // 'Winner'
console.log(Looser.getName());  // 'Winner'
複製代碼

代碼中定義了一個 Singleton 函數,函數在 JavaScript 中是「一等公民「,能夠爲其定義屬性方法。所以咱們能夠在函數 Singleton 中定義一個 getInstance() 方法來管控單例,並建立返回類實例對象,而不是經過傳統的 new 操做符來建立類實例對象。設計模式

this.instance 存儲建立的實例對象,每次接收到建立實例對象時,判斷 this.instance 是否有實例對象,有則返回,沒有則建立並更新 this.instance 值,所以不管調用多少次 getInstance(),最終都只會返回同一個 Singleton 類實例對象。緩存

存在問題:性能優化

  1. 不夠「透明」,沒法使用 new 來進行類實例化,需約束該類實例化的調用方式: Singleton.getInstance(...);
  2. 管理單例的操做,與對象建立的操做,功能代碼耦合在一塊兒,不符合 「單一職責原則」

「透明版」 單例模式:

實現 「透明版」 單例模式,意圖解決:統一使用 new 操做符來獲取單例對象, 而不是 Singleton.getInstance(...)

let CreateSingleton = (function(){
    let instance;
    return function(name) {
        if (instance) {
            return instance;
        }
        this.name = name;
        return instance = this;
    }
})();
CreateSingleton.prototype.getName = function() {
    console.log(this.name);
}

let Winner = new CreateSingleton('Winner');
let Looser = new CreateSingleton('Looser');

console.log(Winner === Looser); // true
console.log(Winner.getName());  // 'Winner'
console.log(Looser.getName());  // 'Winner'
複製代碼

「透明版」單例模式解決了不夠「透明」的問題,咱們又能夠使用 new 操做符來建立實例對象。

「代理版「 單例模式:

經過「代理」的形式,意圖解決:將管理單例操做,與對象建立操做進行拆分,實現更小的粒度劃分,符合「單一職責原則」

let ProxyCreateSingleton = (function(){
    let instance;
    return function(name) {
        // 代理函數僅做管控單例
        if (instance) {
            return instance;
        }
        return instance = new Singleton(name);
    }
})();

// 獨立的Singleton類,處理對象實例
let Singleton = function(name) {
    this.name = name;
}
Singleton.prototype.getName = function() {
    console.log(this.name);
}

let Winner = new PeozyCreateSingleton('Winner');
let Looser = new PeozyCreateSingleton('Looser');

console.log(Winner === Looser); // true
console.log(Winner.getName());  // 'Winner'
console.log(Looser.getName());  // 'Winner'
複製代碼

惰性單例模式

惰性單例,意圖解決:須要時才建立類實例對象。對於懶加載的性能優化,想必前端開發者並不陌生。惰性單例也是解決 「按需加載」 的問題。

需求:頁面彈窗提示,屢次調用,都只有一個彈窗對象,只是展現信息內容不一樣。

開發這樣一個全局彈窗對象,咱們能夠應用單例模式。爲了提高它的性能,咱們可讓它在咱們須要調用時再去生成實例,建立 DOM 節點。

let getSingleton = function(fn) {
    var result;
    return function() {
        return result || (result = fn.apply(this, arguments)); // 肯定this上下文並傳遞參數
    }
}
let createAlertMessage = function(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
}

let createSingleAlertMessage = getSingleton(createAlertMessage);
document.body.addEventListener('click', function(){
    // 屢次點擊只會產生一個彈窗
    let alertMessage = createSingleAlertMessage('您的知識須要付費充值!');
    alertMessage.style.display = 'block';
})
複製代碼

代碼中演示是一個通用的 「惰性單例」 的建立方式,若是還須要 createLoginLayer 登陸框, createFrame Frame框, 均可以調用 getSingleton(...) 生成對應實例對象的方法。

適用場景

「單例模式的特色,意圖解決:維護一個全局實例對象。」

  1. 引用第三方庫(屢次引用只會使用一個庫引用,如 jQuery)
  2. 彈窗(登陸框,信息提高框)
  3. 購物車 (一個用戶只有一個購物車)
  4. 全局態管理 store (Vuex / Redux)

項目中引入第三方庫時,重複屢次加載庫文件時,全局只會實例化一個庫對象,如 jQuerylodashmoment ..., 其實它們的實現理念也是單例模式應用的一種:

// 引入代碼庫 libs(庫別名)
if (window.libs != null) {
  return window.libs;    // 直接返回
} else {
  window.libs = '...';   // 初始化
}
複製代碼

優缺點

  • 優勢:適用於單一對象,只生成一個對象實例,避免頻繁建立和銷燬實例,減小內存佔用。
  • 缺點:不適用動態擴展對象,或需建立多個類似對象的場景。

TIPS: 多線程編程語言中,單例模式會涉及同步鎖的問題。而 JavaScript 是單線程的編程語言,暫可忽略該問題。


參考文章

本文首發Github,期待Star! github.com/ZengLingYon…

做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。

相關文章
相關標籤/搜索