最近在重新修煉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的強大。