JavaScript設計模式 Item 6 --單例模式Singleton

單例模式的定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。javascript

單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏覽器的window對象。在js開發中,單例模式的用途一樣很是普遍。試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸框,而這個浮窗是惟一的,不管單擊多少次登陸按鈕,這個浮窗只會被建立一次。所以這個登陸浮窗就適合用單例模式。html

一、單例模式的使用場景

在使用一種模式以前,咱們最好要知道,這種模式的使用場景。用了這麼久的單例模式,竟全然不知!用它具體有哪些好處呢?java

  • 1.能夠用它來劃分命名空間(這個就是就是常常用的了)設計模式

  • 2.利用分支技術來封裝瀏覽器之間的差別(這個還真沒用過,挺新鮮)數組

  • 3.藉助單例模式,能夠把代碼組織的更爲一致,方便閱讀與維護(這個也用過了)瀏覽器

二、最基本的單例模式

最簡單的單例其實就是一個對象字面量。它把一批有必定關聯的方法和屬性組織在一塊兒。緩存

var Singleton = {
    attr1: true , 
    attr2: 10 ,
    method1 : function(){
        alert('我是方法1');
    },
    method2 : function(){
        alert('我是方法2');
    }
};

這個對象能夠被修改。你能夠添加屬性和方法。你也能夠用delete運算符刪除現有成員。這實際上違背了面向對象設計的一條原則:類能夠被擴展,但不該該被修改。若是某些變量須要保護,那麼能夠將其定義在閉包中。markdown

對象字面量只是建立單例的方法之一。也並不是全部的對象字面量都是單例,那些只是用來模仿關聯數組或容納數據的對象字面量顯然不是單例。閉包

三、借用閉包建立單例

閉包主要的目地 保護數據 單元測試

// 命名空間
var BHX = {} ;
BHX.Singleton = (function(){
    // 添加本身的私有成員
    var a1 = true ;
    var a2 = 10  ;
    var f1 = function(){
        alert('f1');
    }
    var f2 = function(){
        alert('f2');
    }               
    // 把塊級做用域裏的執行結果賦值給個人單例對象
    return {
            attr1: a1 , 
            attr2: a2 ,
            method1 : function(){
                return f1();
            },
            method2 : function(){
                return f2();
            }                       
    } ;
})();

alert(BHX.Singleton.attr1);
BHX.Singleton.method1();

這種單例模式又稱模塊模式,指的是它能夠把一批相關的方法和屬性組織爲模塊並起到劃分命名空間的做用。

四、單例模式用於劃分命名空間

一、防止全局聲明的修改

/*using a namespace*/

var BHX = {};
BHX.Singleton = {
    attr1: true , 
    attr2: 10 ,
    method1 : function(){
        alert('我是方法1');
    },
    method2 : function(){
        alert('我是方法2');
    }               
};
BHX.Singleton.attr1;
var attr1 = false;

這樣以來,即便咱們在外面聲明瞭相同的變量,也能在必定程度上防止attr1的被修改。

二、防止其它來源代碼的修改

如今網頁上的JavaScript代碼每每不止用一個來源,什麼庫代碼、廣告代碼和徽章代碼。爲了不與本身代碼的衝突,能夠定義一個包含本身全部代碼的對象。

var XGP = {};
XGP.Common = {
    //A singleton with common methods used by all objects and modules
}
XGP.ErrorCodes = {
    //An object literal used to store data
}
XGP.PageHandler = {
    //A singleton with page specific methods and attributes.
}

三、用做專用代碼封裝

在擁有許多網頁的網站中,有些代碼是全部網頁都要用到的,他們一般被存放在獨立的文件中;而有些代碼則是某個網頁專用的,不會被用到其餘地方。最好把這兩種代碼分別包裝在本身的單例對象中。

咱們常常要用Javascript爲表單添加功能。出於平穩退化方面的考慮,一般先建立一個不依賴於Javascript的、使用普通提交機制完成任務的純HTML網頁。

XGP.RegPage = {
    FORM_ID: 'reg-form',
    OUTPUT_ID: 'reg-result',

    handleSubmit: function(e){
        e.preventDefault(); //stop the normal form submission

        var data = {};
        var inputs = XGP.RegPage.formEl.getElementByTagName('input');

        for(var i=0, len=inputs.length; i<len; i++){
            data[inputs[i].name] = inputs[i].value;
        }

        XGP.RegPage.sendRegistration(data);
    },
    sendRegistration: function(data){
        //make an xhr request and call displayResult() when response is recieved
        ...
    },
    displayResult: function(response){
        XGP.RegPage.outputEl.innerHTML = response;
    },
    init: function(){
        XGP.RegPage.formEl =$(XGP.RegPage.Form_ID);
        XGP.RegPage.outputEl = $(XGP.RegPage.OUTPUT_ID);
        //hijack the form submission
        addEvent(XGP.RegPage.formEl, 'submit', XGP.RegPage.handleSubmit);
    }
}
//invoke initialization method after the page load
addLoadEvent(XGP.RegPage.init);

五、惰性單例

前面所講的單例模式又一個共同點:單例對象都是在腳本加載時被建立出來。對於資源密集的或配置開銷甚大的單例,更合理的作法是將其實例化推遲到須要使用他的時候。

這種技術就是惰性加載(lazy loading)。

實現步驟以下:

  • 1.將全部代碼移到constructor方法中

  • 2.全權控制調用時機(正是getInstance所要作的)

XGP.lazyLoading = (function(){
    var uniqInstance;

    function constructor(){
        var attr = false;
        function method(){

        }

        return {
            attrp: true,
            methodp: function(){

            }
        }
    }

    return {
        getInstance: function(){
            if(!uniqInstance){
                uniqInstance = constructor();
            }
            return uniqInstance;
        }
    }
})();

六、分支技術

分支是一種用來把瀏覽器間的差別封裝在運行期間進行設置的動態方法中的技術。

// 分支單例 (判斷程序的分支 <瀏覽器差別的檢測>)
var Ext = {} ;
var def =  false ;
Ext.More = (function(){
    var objA = {        // 火狐瀏覽器 內部的一些配置
            attr1:'FF屬性1'
            // 屬性1 
            // 屬性2 
            // 方法1 
            // 方法2
    } ;
    var objB = {        // IE瀏覽器 內部的一些配置
            attr1:'IE屬性1'
            // 屬性1 
            // 屬性2 
            // 方法1 
            // 方法2 
    } ;
    return (def) ?objA:objB;
})();
alert(Ext.More.attr1);

好比說,若是網站中要頻繁使用xhr,每次調用都要再次運行瀏覽器嗅探代碼,這樣會嚴重缺少效率。更有效的作法是在腳本加載時一次性地肯定針對瀏覽器的代碼。這正是分支技術所作的事情。固然,分支技術並不老是更高效的選擇,在兩個或者多個分支中只有一個分支被用到了,其餘分支就佔用了內存。

在考慮是否使用分支技術的時候,必須在縮短期和佔用更多內存這一利一弊之間權衡一下。

下面利用分支技術實現XHR:

var XHR = (function(){
    var standard = {
        createXhrObj: function(){
            return new XMLHttpRequest();
        }
    };
    var activeXNew = {
        createXhrObj: function(){
            return new ActiveXObject('Msxml2.XMLHTTP');
        }
    };
    var activeXOld = {
        createXhrObj: function(){
            return new ActiveXObject('Microsoft.XMLHTTP');
        }
    };

    var testObj;
    try{
        testObj = standard.createXhrObj();
        return testObj;
    }catch(e){
        try{
            testObj = activeXNew.createXhrObj();
            return testObj;
        }catch(e){
            try{
                testObj = activeXOld.createXhrObj();
                return testObj;
            }catch(e){
                throw new Error('No XHR object found in this environment.');
            }
        }
    }
})();

七、單例模式的弊端

瞭解了這麼多關於單例的知識,咱們再來看看它的弊端。

因爲單例模式提供的是一種單點訪問,因此它有可能致使模塊間的強耦合。所以也就不利於單元測試了。

綜上,單例仍是留給定義命名空間和實現分支型方法這些用途。

參考:
JChenJS設計模式—–單例模式

相關文章
相關標籤/搜索