單例模式,是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中,應用該模式的一個類只有一個實例。即一個類只有一個對象實例。javascript
單例模式的簡單實現html
let Singleton = function(name) {
this.name = name;
this.instanse = null;
};
Singleton.prototype.getName = function() {
console.log(this.name);
};
Singleton.prototype.getInstance = function(name) {
if (!this.instanse) {
this.instanse = new Singleton(name);
}
return this.instanse;
}
let instanse1 = Singleton.getInstance('instance');
let instanse2 = Singleton.getInstance('instance');
console.log(instanse1 === instanse2); // true
let instanse3 = new Singleton('instance');
let instanse4 = new Singleton('instance');
console.log(instanse1 === instanse2); // false
複製代碼
這樣雖然實現了單例模式的效果,可是有一個缺陷就是這種方式任然能夠經過new Singleton(name)
來建立新的對象。若是別的程序員不知道這是一個單例類,他可能會經過new
指令來建立對象,而不是經過getInstance
方法java
改進的方法程序員
let Singleton = (function () {
let instance;
let Singleton = function (name) {
if (instance) {
return instance;
}
this.name = name;
return instance = this;
};
return Singleton;
})();
let a = new Singleton('name1');
let b = new Singleton('name2');
console.log(a.name);
console.log(b.name);
console.log(a===b); // true
複製代碼
藉助匿名函數和閉包,實現對instance和Singleton構造函數的封裝設計模式
下面使用這種方式來定義用來建立html中某個惟一元素,例如某個divbash
let SingleDiv = (function () {
let div;
let SingleDiv = function (html) {
if (div) {
return div;
}
this.html = html;
this.init();
return div = this;
};
SingleDiv.prototype.init = function() {
let div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
return SingleDiv;
})();
複製代碼
html測試代碼閉包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<script src="../test.js"></script>
<script> let div1 = new SingleDiv('hello'); let div2 = new SingleDiv('world'); // 最終在屏幕呈現的是hello console.log(div1 === div2); // true </script>
</body>
</html>
複製代碼
可是這種方法也有弊端,違反了單一職責原則,上面的設計方式使得SingleDiv
完成了兩樣職責,建立div,div的初始化。這樣的設計方式不利於修改和維護app
既然是不符合第一職責原則,那麼咱們便把初始化的任務與建立任務分離函數
let CreateDiv = function(html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function() {
let div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
複製代碼
let SingleDiv = (function() {
let div;
return function(html) {
if (!div) {
div = new CreateDiv(html);
}
return div;
}
})()
複製代碼
let div1 = new SingleDiv('div1');
let div2 = new SingleDiv('div1');
console.log(a === b)
複製代碼
因爲js實際上沒有類的概念,因此js中實現單例模式很是簡單。單例模式中只須要一個惟一的對象,並提供全局訪問,因此咱們只須要在全局中建立一個須要的對象便可。雖然實現簡單,可是同時形成了命名空間污染的問題。能夠經過使用命名空間或使用閉包包裝私有變量的方式解決命名空間污染的問題性能
假設須要實現點擊某個按鈕或連接會彈出一個登陸浮窗的效果,爲了性能和維護的方便,須要將其設計成單例的。餓漢式單例模式就是無論是否須要,先建立須要的單例實例。
<!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>Document</title>
</head>
<body>
<button id="loginBtn">
login
</button>
<script> let loginBox = (function() { let div = document.createElement("div"); div.innerText = "this is login window"; div.style.display = "none"; document.body.appendChild(div); return div; })(); document.getElementById("loginBtn").onclick = function() { loginBox.style.display = "block"; } </script>
</body>
</html>
複製代碼
可是這種實現方式是有問題的,由於用戶到這個頁面也許不會進行登陸操做,採用餓漢式單例無論用戶登陸仍是不登陸都會建立一個登陸浮窗div節點,並將其添加到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>Document</title>
</head>
<body>
<button id="loginBtn">
login
</button>
<script> let createLoginBox = (function() { let div; return function() { // 當div不存在時才建立,保證明例惟一 if (!div) { div.innerText = "this is login window"; div.style.display = "none"; document.body.appendChild(div); } return div; } })(); document.getElementById("loginBtn").onclick = function() { let loginBox = createLoginBox(); // 第一次點擊登陸時才建立 loginBox.style.display = "block"; } </script>
</body>
</html>
複製代碼
如上解決方式依然有弊端:違反了單一職責原則;當須要建立惟一的其餘元素時,例如iframe, script標籤,就須要將上述代碼再從新複製一份,只修改建立元素的名稱,這增長了代碼的冗餘和維護成本。
解決方法:將建立對象和保證對象惟一的行爲分離
<!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>Document</title>
</head>
<body>
<button id="loginBtn">
login
</button>
<script>
let getSingleObject = function(fn) {
let result;
return function() {
return result || (result = fn.apply(this, arguments))
}
}
function createDiv() {
let div = document.createElement("div");
div.innerText = "this is login window";
div.style.display = "none";
document.body.appendChild(div);
return div;
}
let createLoginBox = getSingleObject(createDiv);
document.getElementById("loginBtn").onclick = function() {
let loginBox = createLoginBox();
loginBox.style.display = "block";
}
</script>
</body>
</html>
複製代碼
經過上面的方式能夠建立任意一個單例實例