JS設計模式一:單例模式
單例模式也稱作為單子模式,更多的也叫做單體模式。為軟體設計中較為簡單但是最為常用的一種設計模式。
下面是維基百科對單例模式的介紹:
在應用單例模式時,生成單例的類必須保證只有一個例項的存在,很多時候整個系統只需要擁有一個全域性物件,才有利於協調系統整體的行為。比如在整個系統的配置檔案中,配置資料有一個單例物件進行統一讀取和修改,其他物件需要配置資料的時候也統一通過該單例物件來獲取配置資料,這樣就可以簡化複雜環境下的配置管理。
單例模式的思路是:一個類能返回一個物件的引用(並且永遠是同一個)和一個獲得該例項的方法(靜態方法,通常使用 getInstance 名稱)。那麼當我們呼叫這個方法時,如果類持有的引用不為空就返回該引用,否者就建立該類的例項,並且將例項引用賦值給該類保持的那個引用再返回。同時將該類的建構函式定義為私有方法,避免其他函式使用該建構函式來例項化物件,只通過該類的靜態方法來得到該類的唯一例項。
對於 JS 來說,巨大的靈活性使得其可以有多種方式實現單例模式,使用閉包方式來模擬私有資料,按照其思路可得:
var single = (function(){ var unique; function getInstance(){ if( unique === undefined ){ unique = new Construct(); } return unique; } function Construct(){ // ... 生成單例的建構函式的程式碼 } return { getInstance : getInstance } })();
以上,unique便是返回物件的引用,而 getInstance便是靜態方法獲得例項。Construct 便是建立例項的建構函式。
可以通過 single.getInstance() 來獲取到單例,並且每次呼叫均獲取到同一個單例。這就是 單例模式 所實現的效果。
不過,對於JS來說,顯然以上循規蹈矩的方式顯得過於笨重,在不同的場景以不同的方式實現單體模式正是 JS 的優勢
實現1: 最簡單的物件字面量
- var singleton = {
- attr : 1,
- method : function(){ return this.attr; }
- }
- var t1 = singleton ;
- var t2 = singleton ;
那麼很顯然的, t1 === t2 。
十分簡單,並且非常使用,不足之處在於沒有什麼封裝性,所有的屬性方法都是暴露的。對於一些需要使用私有變數的情況就顯得心有餘而力不足了。當然在對於 this 的問題上也是有一定弊端的。
實現2:建構函式內部判斷
其實和最初的JS實現有點類似,不過是將對是否已經存在該類的例項的判斷放入建構函式內部。
- function Construct(){
- // 確保只有單例
- if( Construct.unique !== undefined ){
- return Construct.unique;
- }
- // 其他程式碼
- this.name = "NYF";
- this.age="24";
- Construct.unique = this;
- var t1 = new Construct() ;
- var t2 = new Construct() ;
那麼也有的, t1 === t2 。
也是非常簡單,無非就是提出一個屬性來做判斷,但是該方式也沒有安全性,一旦我在外部修改了Construct的unique屬性,那麼單例模式也就被破壞了。
實現3 : 閉包方式
對於大著 靈活 牌子的JS來說,任何問題都能找到 n 種答案,只不過讓我自己去掂量孰優孰劣而已,下面就簡單的舉幾個使用閉包實現單例模式的方法,無非也就是將建立了的單例快取而已。
var single = (function(){ var unique; function Construct(){ // ... 生成單例的建構函式的程式碼 } unique = new Constuct(); return unique; })();
只要 每次講 var t1 = single; var t2 = single;即可。 與物件字面量方式類似。不過相對而言更安全一點,當然也不是絕對安全。
如果希望會用呼叫 single() 方式來使用,那麼也只需要將內部的 return 改為
return function(){ return unique; }
以上方式也可以使用 new 的方式來進行(形式主義的趕腳)。當然這邊只是給了閉包的一種例子而已,也可以在 Construct 中判斷單例是否存在 等等。 各種方式在各個不同情況做好選著即可。
總結
總的來說,單例模式相對而言是各大模式中較為簡單的,但是單例模式也是較為常用並且很有用的模式。在JS中尤為突出(每個物件字面量都可以看做是一個單例麼~)。
記住,是否嚴格的只需要一個例項物件的類(雖然JS沒有類的概念),那麼就要考慮使用單例模式。
使用資料快取來儲存該單例,用作判斷單例是否已經生成,是單例模式主要的實現思路。
demo:
核心思路:利用Javascript的作用域,形成閉包,從而可以建立私有變數(假設我們將這個私有變數取名為instance),然後將建立的例項賦予這個私有變數instance就ok了。每當想建立這個類的例項時,先判斷instance是否已經引用了存在的例項,如果沒有引用,即這個類沒有被建立例項,so建立一個例項,然後將其賦予給instance;如果instance已經引用,即已存在了該類的例項,so無需再建立,直接使用這個instance就ok了。
第一步:執行匿名函式,防止名稱空間汙染。在匿名函式中,首先定義個上述提到的私有變數instance以及一個類。這個類,我假設它有名字(name)和年齡(age)兩個屬性欄位以及一個輸出他們名字的方(displayInfo)哈。
第二步:利用return + 物件字面量,將我們想,向外暴露的東東,往外拋。
最後,合併第一步第二步的程式碼就形成了一個單例模式啦。
接下來,我們檢驗一下寫的這個單例模式。在上述程式碼中,在類SupposeClass中加入console.log,如果只建立了它的一個例項,那麼就只會列印一個日誌哦。
呼叫兩次getInstance方法,看看列印幾條記錄~
var singletonAccepter=(function(){ var instance=null; function SupposeClass(args){ var args = args||{}; this.name= args.name||'Monkey'; this.age= args.age||24; console.log('this is created!') } SupposeClass.prototype={ constructor:SupposeClass, displayInfo:function(){ console.log('name:'+this.name+' age:'+this.age); } }; return{ name:'SupposeClass', getInstance:function(args){ if(instance===null){ instance=new SupposeClass(args); } return instance; } }; })(); var a=singletonAccepter.getInstance(); var b=singletonAccepter.getInstance(); a.displayInfo(); console.log(a===b);