JavaScript設(shè)計模式之單例模式應(yīng)用場景案例詳解
單例模式
如果希望自己的代碼更優(yōu)雅、可維護(hù)性更高以及更簡潔,往往離不開設(shè)計模式這一解決方案。
在JS設(shè)計模式中,最核心的思想:封裝變化(將變與不變分離,確保變化的部分靈活,不變的部分穩(wěn)定)。
那么來說說第一個常見的設(shè)計模式:單例模式。
單例模式保證一個類僅有一個實(shí)例,并提供一個訪問它的全局訪問方式,為了解決一個全局使用的類頻繁被創(chuàng)建和銷毀、占用內(nèi)存的問題。
ES5中通過閉包
在ES5中,可以使用閉包(函數(shù)內(nèi)部返回函數(shù)被外界變量所引用,導(dǎo)致這個函數(shù)里面的變量無法被釋放,就構(gòu)建成閉包)來保存這個類的實(shí)例。
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 } })()
此時這個實(shí)例一旦生成,每次都是返回這個實(shí)例,且不會被修改,可以看到下面的代碼,當(dāng)給 User 對象初始賦值 name:alice,age:18 時,以后再賦值便無效了,以及每次返回都是初始的實(shí)例對象。
ES6中使用類的靜態(tài)屬性
以上代碼使用ES6語法來實(shí)現(xiàn),通過類的靜態(tài)屬性來保存唯一的實(shí)例對象。
class Singeton { constructor(name,age){ if(!Singeton.instance){ this.name = name; this.age = age; Singeton.instance = this; } return Singeton.instance; } }
創(chuàng)建方式仍然是一樣的,通過 new 關(guān)鍵字創(chuàng)建類的實(shí)例對象。
案例
那這樣一種設(shè)計模式在開發(fā)中實(shí)際有什么用途呢?我們試想這樣一個業(yè)務(wù)場景:訪問網(wǎng)站時,很久沒有操作頁面,此時授權(quán)過期,當(dāng)我們點(diǎn)擊頁面上的任何一個地方,都會彈出一個登錄框。
那么這個登錄框,是全局唯一的,不會存在多份,也不會互相沖突,所以不需要每次都創(chuàng)建一份,保留初始那一份就夠了。
提前創(chuàng)建節(jié)點(diǎn)
我們可能會想到首先在頁面中提前創(chuàng)建節(jié)點(diǎn),編寫好頁面樣式,最后通過控制元素的 display 屬性來達(dá)到顯示和隱藏的效果。
<div class="modal">登錄對話框</div> <button id="open">打開</button> <button id="close">關(guān)閉</button> <style> .modal { display: none; /* 其他布局代碼省略 */ } </style> <script> document.querySelector("#open").onclick = function(){ const modal = document.querySelector('.modal') modal.style.display = 'block' } </script>
這樣可以完成需求,全局只有一個登錄框,每次都展示同一個。但問題是dom元素從一開始它創(chuàng)建好并添加到body中,無論是否需要用到,如果有些場景不需要登陸,那么這里初始渲染就會浪費(fèi)空間。
單例模式
那如果不需要初始渲染,僅當(dāng)需要時才使用,并且每次都返回同一個實(shí)例的單例模式應(yīng)該如何實(shí)現(xiàn)呢?
我們可以這樣處理
<!-- 去除class為modal的標(biāo)簽,動態(tài)創(chuàng)建 --> <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(){ //創(chuàng)建modal,如果放在外面,一開始就會創(chuàng)建元素 const modal = Modal() //顯示modal modal.style.display = "block" } document.querySelector("#close").onclick = function(){ const modal = Modal() modal.style.display = "none" } </script>
雖然上面的方式可以達(dá)到效果,但是創(chuàng)建對象和管理單例的邏輯都放在了對象內(nèi)部,是有些混亂的。并且如果下次需要創(chuàng)建頁面中唯一的 iframe,或者 script 標(biāo)簽,就得將以上函數(shù)照抄一遍。
通用單例
首先拆分函數(shù)邏輯,將執(zhí)行創(chuàng)建對象的邏輯拿出來
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 } })()
以上修改后代碼邏輯就更為清晰,但此時還不支持通用化的創(chuàng)建別的組件,這時候我們想想如何將創(chuàng)建單例的方法進(jìn)行一些優(yōu)化,是否可以將單例需要執(zhí)行的函數(shù)抽象化。
const createSingle = (function(fn){ let instance; return function(){ return instance || ( instance = fn.apply(this, arguments)) } })()
這樣改造后,如果存在創(chuàng)建 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' }
實(shí)際應(yīng)用
以上都是咱小打小鬧的試用,那再來看看社區(qū)中一些非常棒的實(shí)現(xiàn)吧~ 比如:React 中常用的狀態(tài)管理工具 Redux 就使用到了單例模式,它有這樣一些要求。
- 單一數(shù)據(jù)源:整個應(yīng)用的 state 只存在于唯一一個 store 中。
- State 是只讀的:不要直接改變 state 的值,唯一改變 state 的方法就是觸發(fā) action。
- reducer 是純函數(shù):需要編寫純函數(shù) reducer 來修改 state 的值。
來看看 Redux 的源碼,為了便于閱讀已刪減部分邏輯判斷和注釋,可以看到通過 store 的 getState 方法每次獲取閉包中的 currentState。
單例模式在內(nèi)存中只有一個實(shí)例,可以減少內(nèi)存開支,同時還能在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源。
以上就是單例模式的相關(guān)介紹。更多有關(guān) 前端、設(shè)計模式 的內(nèi)容請關(guān)注腳本之家其它相關(guān)文章!
- JavaScript事件發(fā)布/訂閱模式原理與用法分析
- JavaScript實(shí)現(xiàn)與使用發(fā)布/訂閱模式詳解
- JavaScript中發(fā)布/訂閱模式的簡單實(shí)例
- JS前端設(shè)計模式之發(fā)布訂閱模式詳解
- js 發(fā)布訂閱模式的實(shí)例講解
- JavaScript設(shè)計模式之觀察者模式(發(fā)布訂閱模式)原理與實(shí)現(xiàn)方法示例
- JavaScript設(shè)計模式之觀察者模式與發(fā)布訂閱模式詳解
- 詳解JavaScript設(shè)計模式中的享元模式
- JavaScript 設(shè)計模式 安全沙箱模式
- JavaScript設(shè)計模式之觀察者模式(發(fā)布者-訂閱者模式)
- javascript 發(fā)布-訂閱模式 實(shí)例詳解
相關(guān)文章
微信小程序 視圖層(xx.xml)和邏輯層(xx.js)詳細(xì)介紹
這篇文章主要介紹了微信小程序 視圖層(xx.xml)和邏輯層(xx.js)詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-10-10Web?Animations?API實(shí)現(xiàn)一個精確計時的時鐘示例
這篇文章主要為大家介紹了Web?Animations?API實(shí)現(xiàn)一個精確計時的時鐘示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Thinkphp5微信小程序獲取用戶信息接口的實(shí)例詳解
這篇文章主要介紹了Thinkphp5微信小程序獲取用戶信息接口的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09