javascript設計模式-單例模式

最近在重新修煉js的設計模式,發現平時自己所寫的代碼,無意中就使用到了某種的設計模式,所以特意記錄一下,以便以後自己查看。
一.單例模式

單例模式指的是:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

單例模式,是一種很常見的模式,至少在我現在工作中經常用到。單例模式所強調的就是,有且僅有一個對象,並且這個對象是全局變量。那麼,它的使用場景主要集中在登陸彈窗內容提示框loading加載組件等。

二.實現單例模式
想要實現一個標準的單例模式並不複雜,無非就是用一個變量標誌當前是否已經爲某個類創建過對象,如果是,則在下一次獲取該類的實例時,直接返回之前創建的對象。

		  var Singleton = function(name) {
				// 定義一個構造函數
				this.name = name;
				this.instance = null;
			};
			Singleton.prototype.getName = function() {
				// 原型上添加getName方法
				console.log(this.name);
			};
			Singleton.getInstance = function(name) {
				if (!this.instance) {
					// 如果不存在對象,則實例化Singleton
					this.instance = new Singleton(name);
				}
				return this.instance;
			};
			var a = Singleton.getInstance('kafei');
			var b = Singleton.getInstance('lvcha');
			console.log(a === b); // true 同一個對象

三.惰性單例
惰性單例指的是在需要的時候才創建對象實例。假設我們需要做一個唯一的彈窗。
第一種方案是在頁面加載完成的時候便創建好這個div彈窗,這個彈窗一開始肯定是隱藏的狀態,當用戶點擊登陸按鈕的時候,它纔會顯示:

var loginLayer = (function () {
      var div = document.createElement("div");
      div.innerHTML = "登陸彈窗";
      div.style.display = "none";
      document.body.appendChild(div);
      return div;
    })()
    document.getElementById("btn").onclick = function () {
      loginLayer.style.display = "block";
    }

但是這種方式,存在問題,因爲頁面一旦加載,那麼他就已經創建好了節點。如果我不希望它一開始就追加節點,而是用戶點擊按鈕的時候,才創建這個節點。所以,需要改造一下代碼:

var loginLayer = function () {
      var div = document.createElement("div");
      div.innerHTML = "登陸彈窗";
      div.style.display = "none";
      document.body.appendChild(div);
      return div;
    }
    document.getElementById("btn").onclick = function () {
      var loginLayer = loginLayer();
      loginLayer.style.display = "block";
    }

雖然現在已經達到了惰性的目的,但失去了單例的效果。但我們每次點擊按鈕的時候,都會新創建一個新的div,這顯然是一個不合理的情況。
那麼,我們可以使用一個變量來判斷是否已經創建過了div節點。

var createLayer = (function () {
      var div;
      return function () {
        if (!div) {
          div = document.createElement("div");
          div.innerHTML = "登陸彈窗";
          div.style.display = "none";
          document.body.appendChild(div);
        }
        return div;
      }
    })()
    document.getElementById("btn").onclick = function () {
      var loginLayer = createLayer();
      loginLayer.style.display = "block";
    }

四.通用的惰性單例
雖然上面我們已經完成了一個惰性的單例模式,但還是會存在以下問題:
上面代碼違反了單一職責原則,創建對象和管理單例的邏輯都在了createLayer 對象內容。
如果我們下次需要創建頁面中唯一的iframe,則必須如法炮製,把createLayer函數再抄一遍,這顯然是不合理的。

var createIframe = (function () {
      var iframe;
      return function () {
        if (!iframe) {
          iframe = document.createElement("iframe");
          iframe.style.display = "none";
          document.body.appendChild(iframe);
        }
        return iframe;
      }
    })()

現在我們需要不變的部分抽離出來,不去考慮它創建的是div還是一個iframe,創建唯一一個對象的邏輯是一致的,用一個對象來標誌是否創建過對象,如果是則在下次返回這個創建好的對象:

var obj;
if (!obj) {
	obj = xxx;
}

那麼就編寫一個getSingle函數,這個函數接受一個函數(fn)作爲參數,而這個fn這是創建對象的一個方法,然後再利用函數閉包中變量不會被銷燬的特性,來判斷這個對象是否存在。

var getSingel = function (fn) {
      var result;
      return function () {
        return result || (result = fn.apply(this, arguments)); // 保持this不變和能夠接受參數
      }
    }
var loginLayer = function (ele, txt) {
      return function () {
        var oEle = document.createElement(ele);
        if (txt) {
          oEle.innerHTML = txt;
        }
        oEle.style.display = "none";
        document.body.appendChild(oEle);
        return oEle;
      }
    }
    var getSingel = function (fn) {
      var result;
      return function () {
        return result || (result = fn.apply(this, arguments)); // 保持this不變和能夠接受參數
      }
    }
    var createSingleLayer = getSingel(loginLayer("div", "div"));
    // var createIfame = getSingel(loginLayer("iframe", "iframe"));
    document.getElementById("btn").onclick = function () {
      var layer = createSingleLayer();
      layer.style.display = "block";
    }

在這個例子中,我們把創建對象的職責和管理單例的職責放在兩個不同的函數中,這兩個函數相互獨立而互不影響,當它們連接在一起的時候,就完成了創建唯一實例對象的功能。這個不得不感嘆js的強大。

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