js設計模式學習一(單例模式)

寫在最前

爲何會有設計模式這樣的東西存在,那是由於程序設計的不完美,須要用設計模式來彌補設計上的缺陷,那立馬估計會有童鞋問,既然設計的不完美,那就換個完美點的語言,首先,沒有絕對完美的語言存在,其次,借鑑下前輩說的話,靈活而簡單的語言更能激發人們的創造力,因此生命力旺盛,這也可以解釋,近些年來前端發展的如此迅速的緣由吧。
ps一段,自從開始正兒八經深刻學習前端已經有一年多左右了,當時定的一個看書目標就是最初的是dom入門,以後是高三書和犀牛書,截止到如今這三本基本都算看完了,犀牛書後續的一些章節尚未徹底看完,在上個工做的過程當中還算是比較忙的(多是外包項目比較多),中間有準備了面試,如今是在大廠了,發現要學的實在是太多了,本廠的框架一堆一堆的,哎,慢慢來吧,基礎是同樣的區別是實現的思想。
看,這是我當時列的書單
語言精粹這個比較薄,是常常須要翻看,一下看完也記不牢,設計模式這本是湯姆大叔翻譯的,廣泛反映有點晦澀,在技術羣裏有推薦說騰訊大神曾探寫的(JavaScript設計模式與開發實踐)不錯,講的比較容易理解,就買了一本,看了前面三章,講js的面向對象,原型以及閉包的,是設計模式的基礎,以爲還不錯。如今就打算根據看書寫下設計模式的一系列的博客,水平不高,求指正,求交流。

正文:設計模式--單例模式

單例模式的定義是,保證一個類有且僅有一個實例,並提供一個訪問它的全局訪問點。
這個模式是經常使用的模式了,若是結合業務來說的話,就應該是固定的功能,每次調用都沒什麼多大變化的,好比,彈窗的提示,登陸功能的浮窗,

1,實現單例模式

代碼以下javascript

var single = function (name) {
        this.name = name;
        this.instance = null;
    };
    single.prototype.getName = function (){
        alert(this.name);
    };
    single.getInstance = function (name) {
        if( !this.instance ){
            this.instance = new single(name);
        }
        return this.instance;
    };
    var a = single.getInstance('sev1');
    var b = single.getInstance('sev2');
    alert( a === b);

 

咱們經過 single.getInstance 來獲取single類的惟一對象,這種方式相對簡單,但有一個問題,就是增長了這個類的「不透明性」,single類的使用者必須知道這是個單例類,跟以往經過new XXX的方式獲取對象不一樣,這裏是使用single.getInstance來獲取對象。

2,透明的單例模式

咱們如今的目標是實現一個透明的單例類,用戶從這個類中建立對象的時候,能夠像使用其餘任何普通類同樣。在下邊的例子中,咱們將使用creatDiv單例類,它的做用是負責頁面中建立惟一的div節點,代碼以下:
var creatDiv = (function () {
        var instance;
        var creatDiv = function (html) {
            if(instance){
                return instance;
            }
            this.html = html;
            this.init();
            return instance = this;
        };
        creatDiv.prototype.init = function () {
            var div = document.createElement('div');
            div.innerHTML = this.html;
            document.body.appendChild(div);
        };
        return creatDiv;
    })();
    var a = new creatDiv('sev1');
    var b = new creatDiv('sev2');
    alert( a === b );
雖然如今完成了一個透明的單例類的編寫,但它一樣有一些缺點。
爲了把instance封裝起來,使用了自執行函數和閉包,而且讓這個匿名函數返回真正的單例構造方法,這增長了一些程序的複雜度,閱讀起來也不很舒服,
觀察下如今的構造函數,
 var creatDiv = function (html) {
            if(instance){
                return instance;
            }
            this.html = html;
            this.init();
            return instance = this;
        };

 

這個函數負責了兩個事情,第一,建立對象並執行初始化函數,第二,保證只有一個對象,雖然尚未接觸到 「單一職責原則」的概念,就這個構造函數來講,這是一種很差的作法。
假設咱們某天須要利用這個類,在頁面中建立千千萬萬的div,即要讓這個類從單例類變成一個普通的 可產生多個實例的類,那咱們必須改寫creatDiv函數,把控制惟一對象的那一段去掉,這種修改會給咱們帶來沒必要要的煩惱。

3,用代理實現單例模式

如今咱們引入代理的方式來解決上面的問題,
首先咱們把負責管理單例的代碼移除出來,使它成爲一個普通的建立div的類,
var creatDiv = function () {
        this.html = html;
        this.init();
    };
    creatDiv.prototype.init = function () {
        var div = document.createElement('div');
        div.innerHTML = this.html;
        document.body.appendChild(div);
    };

 

接下來引入代理類,
var ProxySingletonCreatDiv = (function () {
        var instance;
        return function (html) {
            if(!instance){
                instance = new creatDiv( html );
            }
            return instance;
        }
    })();
    var a = ProxySingletonCreatDiv('sev1');
    var b = ProxySingletonCreatDiv('sev2');
    alert( a === b );

 

經過引入代理類的方式,完成了一個單例模式的編寫,跟以前不一樣是把負責單例的邏輯和實現建立div的類分開了,這個是緩存代理的應用之一,後續還會有關於代理的一些應用。

4,JavaScript中的單例模式

前面的幾種單例模式的實現,更多的是傳統面嚮對象語言中的實現,單例對象從類中建立而來,在以類爲中心的語言中,這是很天然的作法,好比在java中,若是須要某個對象,就必須先定義一個類,對象老是從類中建立而來。
可是javascript實際上是一門無類的語言,在js中建立對象的方法很是簡單,既然咱們須要一個惟一的對象,爲何還須要先建立一個類呢,傳統的單例模式並不適用與JavaScript。
全局變量不是單例模式,可是在js中咱們常常會把全局變量當成單例模式來用,
例如 var a = {};
當用這種方式建立對象a的時候,對象a是獨一無二的,若是a變量被聲明在全局做用域下,則咱們能夠在代碼中的任何位置使用這個變量,全局變量提供給全局訪問是理所固然的,這樣就知足了單例模式的兩個條件。
做爲一名js開發者,全局變量有多困擾就不用詳細說了,連JavaScript的創造者本人也認可全局變量是設計失誤,在es6中已經有對應的處理方式了。
全局變量的污染也是有解決方式的,

4.1,使用命名空間

只能減小,不能杜絕。例如:
var namespace = {
    a:function(){
        alert(1);
    },
    b:function(){
        alert(2);
    }
}

 

4.2,使用閉包封裝私有變量

這種方法把一些變量封裝在閉包的內部,只暴露一些接口跟外界通訊。
var user = (function () {
        var _name = 'sven',
            _age = 29;
        return {
            getUserInfo : function () {
                return _name + '-' + _age;
            }
        }
    })();

5,惰性單例

前面咱們瞭解了單例模式的一些實現辦法,如今來看下惰性單例。
惰性單例指的是在須要的時候才建立對象的實例,惰性單例是單例模式的重點,這種技術在實際開發中很是有用。就像是一開始的 instance  實例在咱們調用 getInstance  時才被建立,
Singleton.getInstance = (function () {
        var instance = null;
        return function (name) {
            if(!instance){
                instance = new Singleton(name);
            }
            return instance;
        }
    })();

 

不過這是基於類的單例模式,前面已經說過,這種是不適用於javascript的,結合一個登陸彈框的場景,來實現下惰性單例。
第一種解決方案就是,頁面加載的時候就建立好,一開始隱藏,點擊登陸的時候顯示出來。
這種方式有個問題,若是用戶在這個頁面只是想瀏覽其餘內容,不須要登陸,那麼久浪費了一些dom節點。
若是改寫下,在點擊登陸的時候纔開始建立彈窗,
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<button id="btn">登陸</button>
</body>
<script>
    var createLogin = function () {
        var div = document.createElement('div');
        div.innerHTML = '我是登陸彈窗';
        div.style.display = 'none';
        document.body.appendChild(div);
        return div;
    }
    document.getElementById('btn').onclick = function () {
        var login  = createLogin();
        login.style.display = 'block';
    }
</script>
</html>

 

雖然達到了惰性的目的,可是失去了單例的效果,但咱們每次點擊都會新建立一個彈窗,顯然是很差,即便咱們能夠在右上設置個將頁面刪除掉的按鈕,可是頻繁的建立和刪除節點明顯是不合理的。
咱們能夠用變量判斷頁面是否已經建立彈窗,這也是基於類的單例模式的作法。creatLogin就能夠改寫成這樣
 
var createLogin = (function () {
        var div;
        return function () {
            if(!div){
                div = document.createElement('div');
                div.innerHTML = '我是登陸彈窗';
                div.style.display = 'none';
                document.body.appendChild(div);
            }
            return div;
        }
    })();

 

6,通用的惰性單例

上一節,咱們實現了一個可用的惰性單例,可是他仍是有一些問題,這段代碼是違反了單一職責原則的,建立彈窗和管理單例的邏輯都在 creatLogin對象內部,若是咱們須要一個建立iframe的單例,那就要從新將這個函數在寫一遍,能不能將管理單例的邏輯提出來呢,功能的實現是單獨的函數。
管理單例的邏輯都放到getSingle函數裏面,
var getSingle = function (fn) {
        var result;
        return function () {
            return result || (result = fn.apply(this, arguments));
        }
    }

 

建立彈窗的函數就能夠寫成這樣
var createLogin = function () {
        var div = document.createElement('div');
        div.innerHTML = '我是登陸彈窗';
        div.style.display = 'none';
        document.body.appendChild(div);
        return div;
    };

 

creatSingleLogin 就是個惰性單例的函數
var creatSingleLogin = getSingle(creatLogin);
若有其餘實現的,建立iframe等其餘的,均可以由getSingle這個來建立。
其實這種單例模式不僅是建立對象,好比咱們一般渲染完頁面的中的一個列表以後,接下來要給這個列表綁定click事件,若是是經過ajax動態網列表里加數據,在使用事件代理的前提下,click事件實際上只須要在第一次渲染列表的時候綁定一次,可是咱們不想去判斷當前是不是第一次渲染列表,若是藉助jquery,咱們一般選擇給節點綁定one事件:
 var bindEvent = function () {
        $('div').one('click', function () {
            alert('click');
        })
    };
    bindEvent();
    bindEvent();
    bindEvent();

 

雖然函數執行3次,可是綁定事件仍是隻綁定了一次
用getSingle函數也能夠達到同樣的效果,
    var bindEvent = getSingle(function () {
        document.getElementById('btn').addEventListener( 'click', function () { //原書用的onclick,驗證過這個執行幾回也是執行一次,改爲了事件綁定的模式
            console.log('ssss')
        });
        return true;
    });
    bindEvent()
    bindEvent()

 

這樣的話,getSingle的入參函數必需要有個返回值,因此要 return true。
其實,像這樣的場景最好的確定是事件委託,性能方面也會優化不少。

小結

單例模式是咱們學習的第一個模式,咱們先學習了傳統的單例模式的實現,也瞭解由於語言的差別性,有更合適的方法在javascript中建立單例,這一章還提到了代理模式和單一職責原則。
在getSingle函數中,實際上也提到了閉包和高階函數的概念,單例模式是一種簡單但很是實用的模式,特別是惰性單例技術,在合適的時候才建立對象,而且只建立惟一的一個,更奇妙的是建立對象和管理單例的職責被分佈在兩個不一樣的方法中,這兩個方法組合起來纔有單例模式的威力。

參考書

http://book.douban.com/subject/26382780/html

相關文章
相關標籤/搜索