為何要有單例模式
書中有舉出一個(gè)實(shí)際場(chǎng)景,當(dāng)我們點(diǎn)擊登陸按鈕時(shí),頁面中可能會(huì)出現(xiàn)一個(gè)彈框,而這個(gè)彈框是唯一的,無論點(diǎn)多少次登陸按鈕,彈框只會(huì)被創(chuàng)建一次,那么這種情況下就適合用單例模式來創(chuàng)建彈框。
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的單例模式
以下代碼來自書中
var CreateDiv = (function(html) {
var instance
var CreateDiv = function() {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.html
document.appendChild(div)
}
return CreateDiv
})()
以上代碼通過自執(zhí)行函數(shù)和閉包將instance封裝起來。并且返回了真正的Singleton構(gòu)造方法。
通過觀察上面代碼發(fā)現(xiàn)CreateDiv里執(zhí)行了兩個(gè)操作:
1.創(chuàng)建對(duì)象并且執(zhí)行init方法。
2.保證只有一個(gè)對(duì)象。這里就暴露出一個(gè)問題。
如果某天我們需要用這個(gè)方法向頁面中創(chuàng)建更多的元素。那我們必須要改寫CreateDiv,如果我們結(jié)合“單一職責(zé)原則”,我們就知道要去把保證只有一個(gè)對(duì)象這個(gè)操作從CreateDiv抽離出來。這個(gè)目的可以通過代理來實(shí)現(xiàn)。
用代理實(shí)現(xiàn)單例模式
首先我們把上面代碼中的CreateDiv方法改寫成一個(gè)只負(fù)責(zé)創(chuàng)建DIV的類
var CreateDiv = function(html) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.html
document.appendChild(div)
}
接下來引入代理類
var ProxysingletonCreateDiv = (function() {
var instance
return function(html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
var a = new ProxysingletonCreateDiv('test1')
var b = new ProxysingletonCreateDiv('test2')
alert(a === b) // true
至此利用代理類也實(shí)現(xiàn)了一個(gè)單例模式。但目前我們討論的單例模式跟接近傳統(tǒng)面向?qū)ο笳Z言中的實(shí)現(xiàn)。接下來我們來了解一下JavaScript中的單例模式。
JavaScript中的單例模式——惰性單例
了解了單例模式的一些實(shí)現(xiàn)方法之后。我們可以來看看惰性單例的實(shí)現(xiàn),這種實(shí)現(xiàn)方式在JavaScript的實(shí)際編程中是很實(shí)用的。
惰性單例
惰性單例是指在需要的時(shí)候才創(chuàng)建對(duì)象實(shí)例,而不是像之前的代碼那樣,利用自執(zhí)行函數(shù)在代碼執(zhí)行時(shí)就把對(duì)象實(shí)例創(chuàng)建。
比如最開始就提到,當(dāng)打開一個(gè)網(wǎng)站時(shí),需要登錄,但登陸的彈窗只會(huì)在點(diǎn)擊登陸按鈕時(shí)出現(xiàn),甚至有的網(wǎng)站不需要登錄就能直接瀏覽。這時(shí)我們并不需要在頁面加載時(shí)就去創(chuàng)建一個(gè)彈窗。我們大可在需要用的時(shí)候去創(chuàng)建。
<html>
<body>
<button id="loginBtn">登錄</button>
</body>
<script>
var createLoginLayer = (function() {
var div
return function() {
if (!div) {
var div = document.createElement('div')
div.innerHTML = '我是登錄彈窗'
div.style.display = 'none'
document.appendChild(div)
}
return div
}
})
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createLoginLayer()
loginLayer.style.display = 'block'
}
</script>
</html>
以上我們實(shí)現(xiàn)了一個(gè)單例模式的彈窗。但是我們還是可以把其中的控制只有一個(gè)對(duì)象的操作抽離出來,讓我們來實(shí)現(xiàn)一個(gè)通用的惰性單例。
通用惰性單例
通用惰性單例的實(shí)現(xiàn)就是要抽離所有單例模式都要實(shí)現(xiàn)的——控制只有一個(gè)對(duì)象。那么我們來看看控制只有一個(gè)對(duì)象的操作抽象出來是個(gè)什么樣子:
var obj
if (!obj) {
obj = xxx
}
于是就可以把這個(gè)操作的邏輯封裝到一個(gè)getSingle函數(shù)中,然后把要執(zhí)行的函數(shù)當(dāng)作參數(shù)傳入進(jìn)去:
var getSingle = function(fn) {
var result
return function() {
result || (result = fn.apply(this, arguments))
}
}
這樣我們上面寫的創(chuàng)建彈窗的方法就可以完全抽離出來:
var createLoginLayer = function() {
var div = document.createElement('div')
div.innerHTML = '我是登錄彈窗'
div.style.display = 'none'
document.appendChild(div)
return div
}
var createSingleLoginLayer = getsingle(createLoginLayer)
document.getElementById('loginBtn').onclick = function() {
var loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}
至此我們實(shí)現(xiàn)了一個(gè)getSingle函數(shù)來幫我們實(shí)現(xiàn)只有一個(gè)實(shí)例對(duì)象的目的,并且將實(shí)例對(duì)象要做的指責(zé)獨(dú)立出來,兩個(gè)方法互不打擾。