前端設計模式:單例模式(Singleton)

image.png


00、基本概念

單例模式(Singleton Pattern),也稱單體模式,就是全局(或某一作用域範圍)唯一實例,大家共享、複用一個實例對象,也可減少內存開銷。單例模式應該是最基礎、也最常見的設計模式了。

image.png

✅常見場景

  • 全局狀態vuex,Jquery中的全局對象$,瀏覽器中的window、document 都算是單例。
  • 公共的服務、全局配置、緩存、登錄框等,全局複用一個對象。

所以實現單例模式的關鍵就是保障對象實例只創建一次,後續的引用都是同一個實例對象。相比於Java、C#等語言,JavaScript單線程,也沒有類,單例實現還是比較容易,基於JS語言特性,有多種實現思路。

實現方式 說明
全局對象 全局環境下的var變量,或者直接掛載到全局對象window上。使用簡單,但會存在全局污染,也不優雅,🚫不推薦!
構造函數.靜態方法getInstance 使用構造函數的靜態方法getInstance()來獲取實例,唯一實例對象存儲在構造函數的instance上。
雖有一定耦合,Class版本還是一種不錯的方式
閉包-new 利用JS的閉包(萬惡的閉包)來保存那個唯一對象實例,這樣就可以new來獲取唯一實例對象了
ES6模塊Module ES6的模塊其實就是單例模式,模塊中導出的對象就是單例的,多次導入其實是同一個引用。

01、全局對象(不推薦)

創建一個全局對象,瀏覽器中全局對象一般掛載在Window上,如JQuery、loadsh就是如此實現的。

  • 在全局環境中用 var 字面量申明一個對象,利用了var的變量提升 + 全局屬性的特點(全局環境下的var變量會自動成爲全局屬性),所以慎用var
  • 直接掛載到全局對象window上。
window.jQuery = window.$ = jQuery;
window._ = lodash;

// 全局環境下的var變量會自動成爲全局屬性
var singleUser = {
  name: 'sam',
  id: 1001
}
// 使用
console.log(singleUser.name)  // sam
console.log(window.singleUser.name)  // sam

02、構造函數.靜態方法getInstance

統一一個入口獲取對象實例,入口就是爲構造函數的靜態方法getInstance()(當然命名隨意),在該函數中判斷(靜態)對象instance是否初始化,沒有則創建,有則直接返回。所以實際上的唯一實例是作爲靜態屬性,保存在構造器的instance屬性上,類似Math.PI

function GlobalUser(name) {
  this.name = name
  this.id = 1002
}
// 基於構造函數的靜態函數作爲統一入口,Constructor.getInstance()
GlobalUser.getInstance = function(name) {
  // 注意這裏的this指向的是構造函數GlobalUser
  if (this.instance) return this.instance
  // 第一次沒有創建
  return this.instance = new GlobalUser(name)
}
console.log(GlobalUser.getInstance('張三').name)   // 張三
console.log(GlobalUser.getInstance('李四').name)   // 張三,依然是張三,複用了第一次創建的實例
console.log(GlobalUser.getInstance() === GlobalUser.getInstance())  // true

ES6的Class 版本的,原理和上面一樣,因爲Class本質上也是基於原型的構造函數,但實現起來更優雅一些,推薦使用。

class GlobalUser {
  constructor(name) {
    this.name = name
    this.id = 1002
  }
  static getInstance(name) {
    //靜態方法屬於類本身,這裏的this也就指向類本身
    if (!this.instance)
      this.instance = new GlobalUser(name)
    return this.instance;
  }
}
console.log(GlobalUser.getInstance('張三').name)   // 張三
console.log(GlobalUser.getInstance('李四').name)   // 張三,依然是張三,複用了第一次創建的實例
console.log(GlobalUser.getInstance() === GlobalUser.getInstance())  // true

03、閉包-new

核心思路就是利用JS的閉包(萬惡的閉包)來保存那個唯一對象實例,這樣就可以new來獲取唯一實例對象了!基於閉包的實現方式是比較多的,下面示例只是其中一種,但基本原理都是利用閉包來保存那個“唯一實例”。

let GlobalUser = (function() {
  let instance  // 閉包保存的唯一實例對象
  return function(name) {
    if (instance) return instance
    // (首次)創建實例
    instance = { name: '張三', id: 1003 }
    return instance
  }
})()  // 立即執行,外層函數的價值就是他的閉包變量instance
console.log(new GlobalUser('張三').name)   // 張三
console.log(new GlobalUser('李四').name)   // 張三,依然是張三,複用了第一次創建的實例 
console.log(new GlobalUser() === new GlobalUser())  // true

斷點輸出一下日誌可以看到GlobalUser的構造函數閉包

image.png

閉包版本還可以繼續改進下,做成一個通用版本的單例工廠:把具體的對象示例構造器封裝一下。

// 一個通用單例工廠,參數爲構造器函數、Class類
let Singleton = function(Constructor) {
  let instance
  return function(...args) {
    if (instance) return instance
    // (首次)創建實例
    instance = new Constructor(...args)
    return instance
  }
}

// 構造函數
function User(name) {
  this.name = name
}
class Config {
  constructor(title) {
    this.title = title
  }
}
// 使用
let SingleUser = Singleton(User)
let u1 = new SingleUser('sam')
let u2 = new SingleUser('zhangsan')
console.log(u1 === u2, u1, u2)  //true User {name: 'sam'} User {name: 'sam'}

let GlobalConfig = Singleton(Config)
console.log(new GlobalConfig('設計') === new GlobalConfig('模式'))  // true

04、ES6模塊Module

ES6的模塊其實就是單例模式,模塊中導出的對象就是單例的,多次導入其實是同一個引用。

回顧一下ESM:參考《ESModule模塊化

  • 📢 Singleton 模式:import模塊的代碼只會執行一次,同一個url文件只會第一次導入時執行代碼。後續任何地方import都不會執行模塊代碼了,也就是說,import語句是 Singleton 模式的。
  • 📢 只讀-共享:模塊導入的接口的是隻讀的,不能修改。當然引用對象的屬性值是可以修改的,不建議這麼幹,注意模塊是共享的,導出的是一個引用,修改後其他方也會生效。

因此用ESM實現單例就比較簡單了:

// 模塊申明 config.js
export default {
  title: '設計模式'
}

// 使用
import config from './config.js'
console.log(config)  // {title: '設計模式'}
config.title = '修改一下'

import config2 from './config.js'
console.log(config, config2)  // {title: '修改一下'} {title: '修改一下'}


參考資料


©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章