摘要:?jiǎn)卫J街饕菫榱私鉀Q對(duì)象的創(chuàng)建問(wèn)題。頁(yè)面中只放一個(gè)按鈕登錄實(shí)現(xiàn)得到登錄框元素綁定事件關(guān)閉彈框這里做登錄點(diǎn)擊頁(yè)面中的按鈕每次讓登錄框出現(xiàn)即可上面的代碼根據(jù)單例模式的使用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。
最近打算系統(tǒng)的學(xué)習(xí)javascript設(shè)計(jì)模式,以便自己在開(kāi)發(fā)中遇到問(wèn)題可以按照設(shè)計(jì)模式提供的思路進(jìn)行封裝,這樣可以提高開(kāi)發(fā)效率并且可以預(yù)先規(guī)避很多未知的問(wèn)題。
先從最基本的單例模式開(kāi)始。
什么是單例模式單例模式,從名字拆分來(lái)看,單指的是一個(gè),例是實(shí)例,意思是說(shuō)多次通過(guò)某個(gè)類創(chuàng)造出來(lái)實(shí)例始終只返回同一個(gè)實(shí)例,它限制一個(gè)類只能有一個(gè)實(shí)例。單例模式主要是為了解決對(duì)象的創(chuàng)建問(wèn)題。單例模式的特點(diǎn):
一個(gè)類只有一個(gè)實(shí)例
對(duì)外提供唯一的訪問(wèn)接口
在一些以類為核心的語(yǔ)言中,例如java,每創(chuàng)建一個(gè)對(duì)象就必須先定義一個(gè)類,對(duì)象是從類創(chuàng)建而來(lái)。js是一門無(wú)類(class-free)的語(yǔ)言,在js中創(chuàng)建對(duì)象的方法非常簡(jiǎn)單,不需要先定義類即可創(chuàng)建對(duì)象。
在js中,單例模式是一種常見(jiàn)的模式,例如瀏覽器中提供的window對(duì)象,處理數(shù)字的Math對(duì)象。
單例模式的實(shí)現(xiàn) 1. 對(duì)象字面量在js中實(shí)現(xiàn)單例最簡(jiǎn)單的方式是創(chuàng)建對(duì)象字面量,字面量對(duì)象中可以包含多個(gè)屬性和方法。
var mySingleton = { attr1:1, attr2:2, method:function (){ console.log("method"); } }
以上創(chuàng)建一個(gè)對(duì)象,放在全局中,就可以在任何地方訪問(wèn),要訪問(wèn)對(duì)象中的屬性和方法,必須通過(guò)mySingleton這個(gè)對(duì)象,也就是說(shuō)提供了唯一一個(gè)訪問(wèn)接口。
2. 使用閉包私有化擴(kuò)展mySingleton對(duì)象,添加私有的屬性和方法,使用閉包的形式在其內(nèi)部封裝變量和函數(shù)聲明,只暴露公共成員和方法。
var mySingleton = (function (){ //私有變量 var privateVal = "我是私有變量"; //私有函數(shù) function privateFunc(){ console.log("我是私有函數(shù)"); } return { attr1:1, attr2:2, method:function (){ console.log("method"); privateFunc(); } } })()
把privateVal和privateVal被封裝在閉包產(chǎn)生的作用域中,外界訪問(wèn)不到這兩個(gè)變量,這避免了對(duì)全局命名污染。
3.惰性單例無(wú)論使用對(duì)象字面量或者閉包私有化的方式創(chuàng)建單例,都是在腳本一加載就被創(chuàng)建。有時(shí)候頁(yè)面可能不會(huì)用到這個(gè)單例對(duì)象,這樣就會(huì)造成資源浪費(fèi)。對(duì)于這種情況,最佳處理方式是使用惰性單例,也就是在需要這個(gè)單例對(duì)象時(shí)再初始化。
var mySingleton = (function (){ function init(){ //私有變量 var privateVal = "我是私有變量"; //私有函數(shù) function privateFunc(){ console.log("我是私有函數(shù)"); } return { attr1:1, attr2:2, method(){ console.log("method"); privateFunc(); } } } //用來(lái)保存創(chuàng)建的單例對(duì)象 var instance = null; return { getInstance (){ //instance沒(méi)有存值,就執(zhí)行函數(shù)得到對(duì)象 if(!instance){ instance = init(); } //instance存了值,就返回這個(gè)對(duì)象 return instance; } } })(); //得到單例對(duì)象 var singletonObj1 = mySingleton.getInstance(); var singletonObj2 = mySingleton.getInstance(); console.log( singletonObj1 === singletonObj2 ); //true
程序執(zhí)行后,將創(chuàng)建單例對(duì)象的代碼封裝到init函數(shù)中,只暴露了獲取單例對(duì)象的函數(shù)getInstance。當(dāng)有需要用到時(shí),通過(guò)調(diào)用函數(shù)mySingleton.getInstance()得到單例對(duì)象,同時(shí)使用instance將對(duì)象緩存起來(lái),再次調(diào)用mySingleton.getInstance()后得到的是同一個(gè)對(duì)象,這樣通過(guò)一個(gè)函數(shù)不會(huì)創(chuàng)建多個(gè)對(duì)象,起到節(jié)省資源的目的。
4. 使用構(gòu)造函數(shù)可以使用構(gòu)造函數(shù)的方式,創(chuàng)造單例對(duì)象:
function mySingleton(){ //如果緩存了實(shí)例,則直接返回 if (mySingleton.instance) { return mySingleton.instance; } //當(dāng)?shù)谝淮螌?shí)例化時(shí),先緩存實(shí)例 mySingleton.instance = this; } mySingleton.prototype.otherFunc = function (){ console.log("原型上其他方法"); } var p1 = new mySingleton(); var p2 = new mySingleton(); console.log( p1 === p2 ); //true
當(dāng)?shù)谝淮问褂?em>new調(diào)用函數(shù)創(chuàng)建實(shí)例時(shí),通過(guò)函數(shù)的靜態(tài)屬性mySingleton.instance把實(shí)例緩存起來(lái),在第二次用new調(diào)用函數(shù),判斷實(shí)例已經(jīng)緩存過(guò)了,直接返回,那么第一次得到的實(shí)例p1和第二次得到的實(shí)例p2是同一個(gè)對(duì)象。這樣符合單例模式的特點(diǎn):一個(gè)類只能有一個(gè)實(shí)例。
這樣做有一個(gè)問(wèn)題,暴露了可以訪問(wèn)緩存實(shí)例的屬性mySingleton.instance,這個(gè)屬性的值可以被改變:
var p1 = new mySingleton(); //改變mySingleton.instance的值 //mySingleton.instance = null; //或者 mySingleton.instance = {}; var p2 = new mySingleton(); console.log( p1 === p2 ); //false
改變了mySingleton.instance值后,再通過(guò)new調(diào)用構(gòu)造函數(shù)創(chuàng)建實(shí)例時(shí),又會(huì)重新創(chuàng)建新的對(duì)象,那么p1和p2就不是同一個(gè)對(duì)象,違反了單例模式一個(gè)類只能有一個(gè)實(shí)例。
閉包中的實(shí)例不使用函數(shù)的靜態(tài)屬性緩存實(shí)例,而是重新改寫構(gòu)造函數(shù):
function mySingleton(){ //緩存當(dāng)前實(shí)例 var instance = this; //執(zhí)行完成后改寫構(gòu)造函數(shù) mySingleton = function (){ return instance; } //其他的代碼 instance.userName = "abc"; } mySingleton.prototype.otherFunc = function (){ console.log("原型上其他方法"); } var p1 = new mySingleton(); var p2 = new mySingleton(); console.log( p1 === p2 ); //true
第一次使用new調(diào)用函數(shù)創(chuàng)建實(shí)例后,在函數(shù)中創(chuàng)建instance用來(lái)緩存實(shí)例,把mySingleton改寫為另一個(gè)函數(shù)。如果再次使用new調(diào)用函數(shù)后,利用閉包的特性,返回了緩存的對(duì)象,所以p1和p2是同一個(gè)對(duì)象。
這樣雖然也可以保證一個(gè)類只返回一個(gè)實(shí)例,但注意,第二次再次使用new調(diào)用的構(gòu)造函數(shù)是匿名函數(shù),因?yàn)閙ySingleton已經(jīng)被改寫:
//第二次new mySingleton()時(shí)這個(gè)匿名函數(shù)才是真正的構(gòu)造函數(shù) mySingleton = function (){ return instance; }
再次給原mySingleton.prototype上添加是屬性,實(shí)際上這是給匿名函數(shù)的原型添加了屬性:
var p1 = new mySingleton(); //再次給mySingleton的原型上添加屬性 mySingleton.prototype.addAttr = "我是新添加的屬性"; var p2 = new mySingleton(); console.log(p2.addAttr); //undefined
對(duì)象p2訪問(wèn)屬性addAttr并沒(méi)有找到。通過(guò)一個(gè)構(gòu)造函數(shù)構(gòu)造出來(lái)的實(shí)例并不能訪問(wèn)原型上的方法或?qū)傩裕@是一種錯(cuò)誤的做法,還需要繼續(xù)改進(jìn)。
function mySingleton(){ var instance; //改寫構(gòu)造函數(shù) mySingleton = function (){ return instance; } //把改寫后構(gòu)造函數(shù)的原型指向this mySingleton.prototype = this; //constructor改寫為改寫后的構(gòu)造函數(shù) mySingleton.prototype.constructor = mySingleton; //得到改寫后構(gòu)造函數(shù)創(chuàng)建的實(shí)例 instance = new mySingleton; //其他的代碼 instance.userName = "abc"; //顯示的返回改寫后構(gòu)造函數(shù)創(chuàng)建的實(shí)例 return instance; } mySingleton.prototype.otherFunc = function (){ console.log("原型上其他方法"); } var p1 = new mySingleton(); //再次給mySingleton的原型上添加屬性 mySingleton.prototype.addAttr = "我是新添加的屬性"; var p2 = new mySingleton(); console.log(p2.addAttr); //"我是新添加的屬性" console.log( p1 === p2 ); //true
以上代碼主要做了以下幾件事:
改寫mySingleton函數(shù)為匿名函數(shù)
改寫mySingleton的原型為第一次通過(guò)new創(chuàng)建的實(shí)例
因?yàn)楦膶懥藀rototype,要把constructor指回mySingleton
顯式返回通過(guò)改寫后mySingleton構(gòu)造函數(shù)構(gòu)造出的實(shí)例
無(wú)論使用多少次new調(diào)用mySingleton這個(gè)構(gòu)造函數(shù),都返回同一個(gè)對(duì)象,并且這些對(duì)象都共享同一個(gè)原型。
實(shí)踐單例模式 1. 使用命名空間根據(jù)上述,在js中創(chuàng)建一個(gè)對(duì)象就是一個(gè)單例,把一類的方法和屬性放在對(duì)象中,都通過(guò)提供的全局對(duì)象訪問(wèn)。
var mySingleton = { attr1:1, attr2:2, method:function (){ console.log("method"); } }
這樣的方式耦合度極高,例如:要給這個(gè)對(duì)象添加屬性:
mySingleton.width = 1000; //添加一個(gè)屬性 //添加一個(gè)方法會(huì)覆蓋原有的方法 mySingleton.method = function(){};
如果在多人協(xié)作中,這樣添加屬性的方式經(jīng)常出現(xiàn)被覆蓋的危險(xiǎn),可以采用命名空間的方式解決。
//A同學(xué) mySingleton.a = {}; mySingleton.a.method = function(){} //訪問(wèn) mySingleton.a.method(); //B同學(xué) mySingleton.b = {}; mySingleton.b.method = function(){} //訪問(wèn) mySingleton.b.method();
都在自己的命名空間中,覆蓋的幾率會(huì)很小。
可以封裝一個(gè)動(dòng)態(tài)創(chuàng)建命名空間的通用方法,這樣在需要獨(dú)立的命名空間時(shí)只需要調(diào)用函數(shù)即可。
mySingleton.namespace = function(name){ var arr = name.split("."); //存一下對(duì)象 var currentObj = mySingleton; for( var i = 0; i < arr.length; i++ ){ //如果對(duì)象中不存在,則賦值添加屬性 if(!currentObj[arr[i]]){ currentObj[arr[i]] = {}; } //把變量重新賦值,便于循環(huán)繼續(xù)創(chuàng)建命名空間 currentObj = currentObj[arr[i]] } } //創(chuàng)建命名空間 mySingleton.namespace("bom"); mySingleton.namespace("dom.style");
以上調(diào)用函數(shù)生成命名空間的方式代碼等價(jià)于:
mySingleton.bom = {}; mySingleton.dom = {}; mySingleton.dom.style = {};2. 單例登錄框
使用面向?qū)ο髮?shí)現(xiàn)一個(gè)登錄框,在點(diǎn)擊登錄按鈕后登錄框被append到頁(yè)面中,點(diǎn)擊關(guān)閉就將登錄框從頁(yè)面中remove掉,這樣頻繁的操作DOM不合理也不是必要的。
只需要在點(diǎn)擊關(guān)閉時(shí)隱藏登錄框,再次點(diǎn)擊按鈕后,只需要show出來(lái)即可。
頁(yè)面中只放一個(gè)按鈕:
js實(shí)現(xiàn):
function Login(){ var instance; Login = function(){ return install; } Login.prototype = this; install = new Login; install.init(); return install; } Login.prototype.init = function(){ //得到登錄框元素 this.Login = this.createHtml(); document.body.appendChild(this.Login); //綁定事件 this.addEvent(); } Login.prototype.createHtml = function(){ var LoginDiv = document.createElement("div"); LoginDiv.className = "box"; var html = `這里做登錄
` LoginDiv.innerHTML = html; return LoginDiv; } Login.prototype.addEvent = function(){ var close = this.Login.querySelector(".close"); var _this = this; close.addEventListener("click",function(){ _this.Login.style.display = "none"; }) } Login.prototype.show = function(){ this.Login.style.display = "block"; } //點(diǎn)擊頁(yè)面中的按鈕 var loginBtn = document.querySelector("#loginBtn"); loginBtn.onclick = function(){ var login = new Login(); //每次讓登錄框出現(xiàn)即可 login.show(); }
上面的代碼根據(jù)單例模式的使用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。這樣在一開(kāi)始生成了一個(gè)對(duì)象,之后使用的都是同一個(gè)對(duì)象。
總結(jié)單例模式是一種非常實(shí)用的模式,特別是懶性單例技術(shù),在合適時(shí)候創(chuàng)建對(duì)象,并且只創(chuàng)建唯一一個(gè),這樣減少不必要的內(nèi)存消耗。
正在學(xué)習(xí)設(shè)計(jì)模式,不正確的地方歡迎拍磚指正。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81640.html
摘要:?jiǎn)卫J街饕菫榱私鉀Q對(duì)象的創(chuàng)建問(wèn)題。頁(yè)面中只放一個(gè)按鈕登錄實(shí)現(xiàn)得到登錄框元素綁定事件關(guān)閉彈框這里做登錄點(diǎn)擊頁(yè)面中的按鈕每次讓登錄框出現(xiàn)即可上面的代碼根據(jù)單例模式的使用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。 showImg(https://segmentfault.com/img/bVbiE4g?w=568&h=450);最近打算系統(tǒng)的學(xué)習(xí) Javascript 設(shè)計(jì)模式,以便自己在開(kāi)發(fā)中遇到問(wèn)題可以按照...
摘要:但是,這并不是采用單例的唯一原因。使用命名空間單例模式也被稱為模塊設(shè)計(jì)模式。函數(shù)內(nèi)部聲明了一些局部函數(shù)和或變量。緊隨函數(shù)聲明放置即可立即執(zhí)行外部函數(shù),并將所得的對(duì)象文字費(fèi)賠給變量。 JavaScript設(shè)計(jì)模式-第一部分:?jiǎn)卫J?、組合模式和外觀模式 設(shè)計(jì)模式是一些可靠的編程方式,有助于保證代碼更加易于維護(hù)、擴(kuò)展及分離,所有設(shè)計(jì)模式在創(chuàng)建大型JavaScript應(yīng)用程序時(shí)均不可或缺 單...
摘要:此時(shí)我們創(chuàng)建的對(duì)象內(nèi)保存靜態(tài)變量通過(guò)取值器訪問(wèn),最后將這個(gè)對(duì)象作為一個(gè)單例放在全局空間里面作為靜態(tài)變量單例對(duì)象供他人使用。 單例模式 又被稱為單體模式,是只允許實(shí)例化一次的對(duì)象類。有時(shí)我們也用一個(gè)對(duì)象來(lái)規(guī)劃一個(gè)命名空間,井井有條的管理對(duì)象上面的屬性和方法。 傳統(tǒng)的面向?qū)ο笳Z(yǔ)言中單例模式的實(shí)現(xiàn),均是單例對(duì)象從類中創(chuàng)建而來(lái),在以類為中心的語(yǔ)言中,這是很常見(jiàn)的做法。如果需要某個(gè)對(duì)象,就必須先...
摘要:不符合設(shè)計(jì)模式中的單一職責(zé)的概念。引入代理實(shí)現(xiàn)單例模式引入代理實(shí)現(xiàn)單例模式的特點(diǎn)我們負(fù)責(zé)管理單例的邏輯移到了代理類中。的單例模式對(duì)比在以上的代碼中實(shí)現(xiàn)的單例模式都混入了傳統(tǒng)面向?qū)ο笳Z(yǔ)言的特點(diǎn)。 聲明:這個(gè)系列為閱讀《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》 ----曾探@著一書的讀書筆記 1.單例模式的特點(diǎn)和定義 保證一個(gè)類僅有一個(gè)實(shí)例,并且提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。 2.傳統(tǒng)面向?qū)?..
摘要:停更許久,近期計(jì)劃更新設(shè)計(jì)模式系列。單例模式是創(chuàng)建型設(shè)計(jì)模式的一種。雖然它不是正規(guī)的單例模式,但不可否認(rèn)確實(shí)具備類單例模式的特點(diǎn)。適用場(chǎng)景單例模式的特點(diǎn),意圖解決維護(hù)一個(gè)全局實(shí)例對(duì)象。 停更許久,近期計(jì)劃更新:設(shè)計(jì)模式系列。 showImg(https://segmentfault.com/img/bVbt7uw?w=800&h=600); 單例模式:限制類實(shí)例化次數(shù)只能一次,一個(gè)類只...
閱讀 892·2021-10-13 09:39
閱讀 1481·2021-10-11 10:57
閱讀 2589·2019-08-26 13:53
閱讀 2538·2019-08-26 12:23
閱讀 3681·2019-08-23 18:30
閱讀 3745·2019-08-23 18:08
閱讀 2524·2019-08-23 18:04
閱讀 2959·2019-08-23 16:28