如果希望自己的代碼更優雅、可維護性更高以及更簡潔,往往離不開設計模式這一解決方案。 在JS設計模式中,最核心的思想:封裝變化(將變與不變分離,確保變化的部分靈活,不變的部分穩定)。 單例模式 那麼來說說第一個常見的設計模式:單例模式。 單例模式保證一個類僅有一個實例,並提供一個訪問它的全局訪問方式, ...
如果希望自己的代碼更優雅、可維護性更高以及更簡潔,往往離不開設計模式這一解決方案。
在JS設計模式中,最核心的思想:封裝變化(將變與不變分離,確保變化的部分靈活,不變的部分穩定)。
單例模式
那麼來說說第一個常見的設計模式:單例模式。
單例模式保證一個類僅有一個實例,並提供一個訪問它的全局訪問方式,為瞭解決一個全局使用的類頻繁被創建和銷毀、占用記憶體的問題。
ES5中通過閉包
在ES5中,可以使用閉包(函數內部返回函數被外界變數所引用,導致這個函數裡面的變數無法被釋放,就構建成閉包)來保存這個類的實例。
var Singeton = (function(){
var instance;
function User(name,age){
this.name=name;
this.age=age;
}
return function(name,age){
if(!instance){
instance = new User(name,age)
}
return instance
}
})()
此時這個實例一旦生成,每次都是返回這個實例,且不會被修改,可以看到下麵的代碼,當給 User 對象初始賦值 name:alice,age:18 時,以後再賦值便無效了,以及每次返回都是初始的實例對象。
ES6中使用類的靜態屬性
以上代碼使用ES6語法來實現,通過類的靜態屬性來保存唯一的實例對象。
class Singeton {
constructor(name,age){
if(!Singeton.instance){
this.name = name;
this.age = age;
Singeton.instance = this;
}
return Singeton.instance;
}
}
創建方式仍然是一樣的,通過 new 關鍵字創建類的實例對象。
案例
那這樣一種設計模式在開發中實際有什麼用途呢?我們試想這樣一個業務場景:訪問網站時,很久沒有操作頁面,此時授權過期,當我們點擊頁面上的任何一個地方,都會彈出一個登錄框。
那麼這個登錄框,是全局唯一的,不會存在多份,也不會互相衝突,所以不需要每次都創建一份,保留初始那一份就夠了。
提前創建節點
我們可能會想到首先在頁面中提前創建節點,編寫好頁面樣式,最後通過控制元素的 display 屬性來達到顯示和隱藏的效果。
<div class="modal">登錄對話框</div>
<button id="open">打開</button>
<button id="close">關閉</button>
<style>
.modal {
display: none;
/* 其他佈局代碼省略 */
}
</style>
<script>
document.querySelector("#open").onclick = function(){
const modal = document.querySelector('.modal')
modal.style.display = 'block'
}
</script>
這樣可以完成需求,全局只有一個登錄框,每次都展示同一個。但問題是dom元素從一開始它創建好並添加到body中,無論是否需要用到,如果有些場景不需要登陸,那麼這裡初始渲染就會浪費空間。
單例模式
那如果不需要初始渲染,僅當需要時才使用,並且每次都返回同一個實例的單例模式應該如何實現呢?
我們可以這樣處理
<!-- 去除class為modal的標簽,動態創建 -->
<script>
const Modal = (function(){
let instance = null
return function(){
if(!instance){
instance = document.createElement("div")
instance.innerHTML = "登錄對話框"
instance.className = "modal"
instance.style.display = "none"
document.body.appendChild(instance)
}
return instance
}
})()
document.querySelector("#open").onclick = function(){
//創建modal,如果放在外面,一開始就會創建元素
const modal = Modal()
//顯示modal
modal.style.display = "block"
}
document.querySelector("#close").onclick = function(){
const modal = Modal()
modal.style.display = "none"
}
</script>
雖然上面的方式可以達到效果,但是創建對象和管理單例的邏輯都放在了對象內部,是有些混亂的。並且如果下次需要創建頁面中唯一的 iframe,或者 script 標簽,就得將以上函數照抄一遍。
通用單例
首先拆分函數邏輯,將執行創建對象的邏輯拿出來
const createLayer = function(){
let div = document.createElement("div")
div.innerHTML = "登錄對話框"
div.className = "modal"
div.style.display = "none"
document.body.appendChild(div);
return div;
}
const Modal = (function(){
let instance = null
return function(){
if(!instance){
instance = createLayer()
}
return instance
}
})()
以上修改後代碼邏輯就更為清晰,但此時還不支持通用化的創建別的組件,這時候我們想想如何將創建單例的方法進行一些優化,是否可以將單例需要執行的函數抽象化。
const createSingle = (function(fn){
let instance;
return function(){
return instance || ( instance = fn.apply(this, arguments))
}
})()
這樣改造後,如果存在創建 iframe 的方法,也可以直接使用。
const createIframe = function() {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
return iframe
}
const singleIframe = createSingle(createIframe)
document.querySelector("#open").onclick = function(){
const iframe = singleIframe()
iframe.style.display = 'block'
}
實際應用
以上都是咱小打小鬧的試用,那再來看看社區中一些非常棒的實現吧~ 比如:React 中常用的狀態管理工具 Redux 就使用到了單例模式,它有這樣一些要求。
- 單一數據源:整個應用的 state 只存在於唯一一個 store 中。
- State 是只讀的:不要直接改變 state 的值,唯一改變 state 的方法就是觸發 action。
- reducer 是純函數:需要編寫純函數 reducer 來修改 state 的值。
來看看 Redux 的源碼,為了便於閱讀已刪減部分邏輯判斷和註釋,可以看到通過 store 的 getState 方法每次獲取閉包中的 currentState。
單例模式在記憶體中只有一個實例,可以減少記憶體開支,同時還能在系統設置全局的訪問點,優化和共用資源。
以上就是單例模式的相關介紹。更多有關 前端
、設計模式
的內容可以參考我其它的博文,持續更新中~