深入理解 JavaScript 單例模式 (Singleton Pattern)

G_Owen發表於2019-07-22

概念

單例模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例物件的類必須保證只有一個例項存在。

核心:確保只有一個例項,並提供全域性訪問。

實現思路

一個類能返回物件一個引用(永遠是同一個)和一個獲得該例項的方法(必須是靜態方法,通常命名為getIntance);當我們呼叫這個方法時,類持有的引用不為空則返回這個引用,如果為空須建立該類例項並將例項的引用賦予該類保持的引用;同時將該類的建構函式定義為私有方法,那麼其他環境就無法通過呼叫該類的建構函式來例項化該類的物件,只能通過該類提供的靜態方法得到該類唯一的例項。

實現 Java 語言中的單例模式

  public class Singleton {
      private static final Singleton {
          private Singleton() { };

          public static Singleton getInstance {
              if (INSTANCE == null) {
                  synchronized(Singleton.class) {
                      if(INSTANCE = null) {
                         INSTANCE = new Singleton()
                      }
                  }

              }
              return INSTANCE;
          }
      }
  }

實現 JavaScript 語言中的單例模式

let Singleton = function(name){
    this.name = name;
}
Singleton.prototype.getName = function() {
    console.log(this.name)
}
Singleton.getInstance = (function() {
   let instance;
   return function(name) {
       if(instance) return instance;
       return instance = new Singleton(name)
   }
})()
let s1 = Singleton.getInstance('owen'); // Singleton {name: "owen"}
let s2 = Singleton.getInstance('guowen'); // Singleton.getInstance('guowen');
s1 === s2 // true

JavaScript中單例作為一個名稱空間提供者,從全域性名稱空間裡提供一個唯一的訪問點來訪問該物件。

應用

名稱空間

使用名稱空間可以降低全域性變數帶來的命名汙染;
最簡單的方法是物件字面量

const globalWeb = {
    a() {},
    b() {}
    // ...
}

或者使用閉包

let Singleton = (function(){
    let instance;
    let init = function() {
    let name = 'owen';
        return {
            name,
            data(){
                return {}
            },
            method:{ }
        }
    }
    return {
        getInstance() {
            if(instance) return instance;
            return instance = init()
        }
    }
}())
let app = Singleton.getInstance() // {name: "owen", data: ƒ, method: {…}}

惰性單例

惰性單例指在需要的時候才建立物件例項,在實現開發中非常有用,即目標物件只有在使用的時候才被建立,而不是頁面載入好時建立。

模態框示例

點選一個按鈕彈窗一個模態框,很明顯頁面是唯一的,一次不會彈窗多個模態框的情況

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>modal box</title>
    <style>
    * {
        margin: 0;
        padding:0;
    }
   html{
   width:100%;
   height:100%;
   }
    .Owen {
        width:30%;
        height:30%;
        margin:10% auto;
    }
    #modal {
        width:100%;
        height:100%;
        position:fixed;
        left:0;
        top:0;
        background: rgba(0, 0, 0, 0.52);
        display:none;
    }
    .main{
        width:30%;
        height:30%;
        margin:10% auto;
        text-align: center;
        background-color: #b0e8ff;
    }
    </style>
</head>
<body>
   <div class="Owen">
        <button>Owen</button>
   </div>
    <div id="modal">
        <div class="main">
          <div>
                我是彈框
          </div>
        </div>
    </div>
</body>
<script>

window.onload = function(){
    let openModal = document.querySelector("button")
    let modal = document.querySelector("#modal")
    openModal.addEventListener('click',function(){
       modal.style.display = 'block'
    })
}

</script>
</html>

第一種方法是在頁面載入完成時建立好這個彈框,一開始就是隱藏的,只有點選按鈕的時候才顯示,這種方式有一個問題,就是我們進入頁面,只是看看其他內容,不做任何操作;這樣就造成資源浪費

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>modal box</title>
    <style>
    * {
        margin: 0;
        padding:0;
    }
   html{
   width:100%;
   height:100%;
   }
    .Owen {
        width:30%;
        height:30%;
        margin:10% auto;
    }
    #modal {
        width:100%;
        height:100%;
        position:fixed;
        left:0;
        top:0;
        background: rgba(0, 0, 0, 0.52);
    }
    .main{
        width:30%;
        height:30%;
        margin:10% auto;
        padding:20px;
        text-align: right;
        background-color: #b0e8ff;
        position:relative;
    }
    .main div {
        text-align: center;
    }
    .main span {
        display: inline-block;
        padding:5px;
        cursor:pointer;
    }
    </style>
</head>
<body>

    <div class="Owen">
        <button>點我</button>
    </div>

</body>
<script>
window.onload = function(){
    init()
}
function init() {
    let openModal = document.querySelector("button")
    let createModal =( function() {
        let flag;
        // 生成 Modal 容器
        let div = document.createElement('div')
        div.id = "modal"
        div.style.display = "none";
        return function() {
            if(flag) return div;
            flag = true;
            let fra = document.createDocumentFragment();
            // 新增 Modal 內容
            els = `<div class="main">
                    <span class="close">×</span>
                        <div>
                            我是彈框
                        </div>
                    </div>
                    `
            div.innerHTML = els;
            fra.appendChild(div)
            document.body.appendChild(fra)
            // 關閉 Modal
            let close = document.querySelector('.close')
            close.addEventListener('click',function(){
                flag = false;
                document.body.removeChild(div)
            })

            return div
        }
    }())
    // 顯示 Modal
    openModal.addEventListener('click',function(){
       createModal().style.display = "block";
    })
}
</script>
</html>

第二種方法,只執行一次DOM的建立修改操作,不用頻繁的建立和刪除節點,提高資源利用率;

個人部落格

參考資料

《JavaScript設計模式與開發實踐》
基維

大叔

相關文章