javascript設計模式——單例模式

来源:http://www.cnblogs.com/xiaohuochai/archive/2017/12/13/8029196.html
-Advertisement-
Play Games

[1]標準單例 [2]透明單例 [3]代理實現單例 [4]惰性單例 [5]通用惰性單例 ...


前面的話

  單例模式是指保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 單例模式是一種常用的模式,有一些對象往往只需要一個,比如線程池、全局緩存、瀏覽器中的window對象等。在javaScript開發中,單例模式的用途同樣非常廣泛。試想一下,單擊登錄按鈕時,頁面中會出現一個登錄浮窗,而這個登錄浮窗是唯一的,無論單擊多少次登錄按鈕,這個浮窗都只會被創建一次,那麼這個登錄浮窗就適合用單例模式來創建

 

標準單例

  要實現一個標準的單例模式並不複雜,無非是用一個變數來標誌當前是否已經為某個類創建過對象,如果是,則在下一次獲取該類的實例時,直接返回之前創建的對象。代碼如下:

var Singleton = function( name ){ 
  this.name = name; 
  this.instance = null;
};
Singleton.prototype.getName = function(){ 
  alert ( this.name );
};
Singleton.getInstance = function( name ){ 
  if ( !this.instance ){
    this.instance = new Singleton( name );
  }
  return this.instance;
};
var a = Singleton.getInstance( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' );
alert ( a === b );    // true

  或者:

var Singleton = function( name ){ 
  this.name = name;
};
Singleton.prototype.getName = function(){ 
  alert ( this.name );
};
Singleton.getInstance = (
  function(){ 
    var instance = null;
    return function( name ){
      if ( !instance ){
        instance = new Singleton( name );
      }
    })();
  }
  return instance;

  通過Singleton.getInstance來獲取Singleton類的唯一對象,這種方式相對簡單,但有一個問題,就是增加了這個類的“不透明性”,Singleton類的使用者必須知道這是一個單例類,跟以往通過new XXX的方式來獲取對象不同,這裡偏要使用Singleton.getInstance來獲取對象

  雖然已經完成了一個單例模式的編寫,但這段單例模式代碼的實際意義並不大

 

透明單例

  現在的目標是實現一個“透明”的單例類,用戶從這個類中創建對象時,可以像使用其他任何普通類一樣。在下麵的例子中,將使用CreateDiv單例類,它的作用是負責在頁面中創建唯一的div節點,代碼如下

  var CreateDiv = (function () {
    var instance;
    var CreateDiv = function (html) {
      if (instance) {
        return instance;
      }
      this.html = html;
      this.init();
      return instance = this;
    };
    CreateDiv.prototype.init = function () {
      var div = document.createElement('div');
      div.innerHTML = this.html;
      document.body.appendChild(div);
    };
    return CreateDiv;
  })();

  var a = new CreateDiv('sven1');
  var b = new CreateDiv('sven2');
  alert(a === b);    // true

  雖然現在完成了一個透明的單例類的編寫,但它同樣有一些缺點。為了把instance封裝起來,使用了自執行的匿名函數和閉包,並且讓這個匿名函數返回真正的Singleton構造方法,這增加了一些程式的複雜度,閱讀起來也不是很舒服

  上面的代碼中,CreateDiv構造函數實際上負責了兩件事情。第一是創建對象和執行初始化init方法,第二是保證只有一個對象。這是一種不好的做法,至少這個構造函數看起來很奇怪。假設某天需要利用這個類,在頁面中創建千千萬萬的div,即要讓這個類從單例類變成一個普通的可產生多個實例的類,那必須得改寫CreateDiv構造函數,把控制創建唯一對象的那一段去掉,這種修改會帶來不必要的煩惱

 

代理實現單例

  現在通過引入代理類的方式,來解決上面提到的問題。依然使用上面的代碼,首先在CreateDiv構造函數中,把負責管理單例的代碼移除出去,使它成為一個普通的創建div的類

  var CreateDiv = function (html) {
    this.html = html;
    this.init();
  };
  CreateDiv.prototype.init = function () {
    var div = document.createElement('div');
    div.innerHTML = this.html;
    document.body.appendChild(div);
  };
  //引入代理類proxySingletonCreateDiv
  var ProxySingletonCreateDiv = (function () {
    var instance;
    return function (html) {
      if (!instance) {
        instance = new CreateDiv(html);
      }
      return instance;
    }
  })();
  var a = new ProxySingletonCreateDiv('sven1');
  var b = new ProxySingletonCreateDiv('sven2');
  alert(a === b);

  通過引入代理類的方式,同樣完成了一個單例模式的編寫,跟之前不同的是,現在把負責管理單例的邏輯移到了代理類proxySingletonCreateDiv中。這樣一來,CreateDiv就變成了一個普通的類,它跟proxySingletonCreateDiv組合起來可以達到單例模式的效果

 

惰性單例

  惰性單例指的是在需要的時候才創建對象實例。惰性單例是單例模式的重點,這種技術在實際開發中非常有用

  下麵繼續以登錄框的例子來說明

<button id="loginBtn">登錄</button>
<script>
    var loginLayer = (function () {
      var div = document.createElement('div');
      div.innerHTML = '我是登錄浮窗';
      div.style.display = 'none';
      document.body.appendChild(div);
      return div;
    })();
    document.getElementById('loginBtn').onclick = function () {
      loginLayer.style.display = 'block';
    };
</script>  

  這種方式有一個問題,如果根本不需要進行登錄操作,登錄浮窗一開始就被創建好,很有可能將白白浪費一些 DOM 節點

  現在改寫一下代碼,使用戶點擊登錄按鈕的時候才開始創建該浮窗

<button id="loginBtn">登錄</button>
<script>
    var createLoginLayer = function () {
      var div = document.createElement('div');
      div.innerHTML = '我是登錄浮窗';
      div.style.display = 'none';
      document.body.appendChild(div);
      return div;
    };
    document.getElementById('loginBtn').onclick = function () {
      var loginLayer = createLoginLayer();
      loginLayer.style.display = 'block';
    };
</script>  

  雖然現在達到了惰性的目的,但失去了單例的效果。每次點擊登錄按鈕時,都會創建一個新的登錄浮窗div

  可以用一個變數來判斷是否已經創建過登錄浮窗,代碼如下

    var createLoginLayer = (function(){
        var div;
        return function(){
            if ( !div ){
                div = document.createElement( 'div' );
                div.innerHTML = '我是登錄浮窗';
                div.style.display = 'none';
                document.body.appendChild( div );
            }
            return div;
        }
    })();
    document.getElementById( 'loginBtn' ).onclick = function(){
        var loginLayer = createLoginLayer();
        loginLayer.style.display = 'block';
    };

  上面的代碼仍然存在如下問題:

  1、違反單一職責原則的,創建對象和管理單例的邏輯都放在 createLoginLayer對象內部

  2、如果下次需要創建頁面中唯一的iframe,或者script標簽,用來跨域請求數據,就必須得如法炮製,把createLoginLayer函數幾乎照抄一遍

    var createIframe= (function(){
        var iframe;
        return function(){
            if ( !iframe){
                iframe= document.createElement( 'iframe' );
                iframe.style.display = 'none';
                document.body.appendChild( iframe);
            }
            return iframe;
        }
    })();

 

通用惰性單例

  現在需要把不變的部分隔離出來,先不考慮創建一個div和創建一個iframe有多少差異,管理單例的邏輯其實是完全可以抽象出來的,這個邏輯始終是一樣的:用一個變數來標誌是否創建過對象,如果是,則在下次直接返回這個已經創建好的對象

var obj;
if ( !obj ){ 
  obj = xxx;
}

  然後,把如何管理單例的邏輯從原來的代碼中抽離出來,這些邏輯被封裝在getSingle函數內部,創建對象的方法fn被當成參數動態傳入getSingle函數

var getSingle = function( fn ){ 
  var result;
  return function(){
    return result || ( result = fn .apply(this, arguments ) );
  }
}

  接下來將用於創建登錄浮窗的方法用參數fn的形式傳入getSingle,不僅可以傳入createLoginLayer,還能傳入createScript、createIframe、createXhr等。之後再讓getSingle返回一個新的函數,並且用一個變數result來保存fn的計算結果。result變數因為身在閉包中,它永遠不會被銷毀。在將來的請求中,如果result已經被賦值,那麼它將返回這個值

    var createLoginLayer = function(){
        var div = document.createElement( 'div' );
        div.innerHTML = '我是登錄浮窗';
        div.style.display = 'none';
        document.body.appendChild( div );
        return div;
    };
    var createSingleLoginLayer = getSingle( createLoginLayer );
    document.getElementById( 'loginBtn' ).onclick = function(){
        var loginLayer = createSingleLoginLayer();
        loginLayer.style.display = 'block';
    };

  下麵再試試創建唯一的iframe用於動態載入第三方頁面

    var createSingleIframe = getSingle(function () {
      var iframe = document.createElement('iframe');
      document.body.appendChild(iframe);
      return iframe;
    });
    document.getElementById('loginBtn').onclick = function () {
      var loginLayer = createSingleIframe();
      loginLayer.src = 'https://www.hao123.com';
    };

  上面的例子中,創建實例對象的職責和管理單例的職責分別放置在兩個方法里,這兩個方法可以獨立變化而互不影響,當它們連接在一起的時候,就完成了創建唯一實例對象的功能

  這種單例模式的用途遠不止創建對象,比如通常渲染完頁面中的一個列表之後,接下來要給這個列表綁定click事件,如果是通過ajax動態往列表裡追加數據,在使用事件代理的前提下,click事件實際上只需要在第一次渲染列表的時候被綁定一次,但不想判斷當前是否是第一次渲染列表,如果藉助於jQuery,通常選擇給節點綁定one事件

    var bindEvent = function(){
        $( 'div' ).one( 'click', function(){
            alert ( 'click' );
        });
    };
    var render = function(){
        console.log( '開始渲染列表' );
        bindEvent();
    };
    render();
    render();
    render();

  如果利用getSingle函數,也能達到一樣的效果

    var getSingle = function (fn) {
        var result;
        return function () {
            return result || (result = fn.apply(this, arguments));
        }
    };
    var bindEvent = getSingle(function(){
        document.getElementById( 'div1' ).onclick = function(){
            alert ( 'click' );
        }
        return true;
    });
    var render = function(){
        console.log( '開始渲染列表' );
        bindEvent();
    };
    render();
    render();
    render();

  可以看到,render函數和bindEvent函數都分別執行了3次,但div實際上只被綁定了一個事件

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 執行結果: 第二段sql 執行結果: 以上結果表明: exec(@sqlstr) 其中@sqlstr中如果包含變數的運算,是將變數轉換為varchar後再exec操作 ...
  • iOS提供3種不同的定位途徑: 1,WiFi定位,通過查詢一個WiFi路由器的地理位置信息,比較省電;iPhone,iPod touch和iPad都可以採用; 2,蜂窩式行動電話基站定位,通過移動運營商基站定位,只有iPhone,3G版本的iPod touch 和iPad可以採用。 3,GPS衛星定 ...
  • 導入後gradle building 一直到跑,卡住了,一般是gradle沒有下載,又下不下來的原因。 去 http://services.gradle.org/distributions/ 下載 5.6 需要導出的工程的gradle gradle-2.10-all, gradle-2.14.1-a ...
  • 在ios7中有一種扁平風格的控制項叫做分段選擇控制項UISegmentedControl,控制項分為一排,橫放著幾個被簡單線條隔開的按鈕,每次點擊只能選擇其中一個按鈕,他類似於tabbar但是又稍微有點區別,新版的qq手機客戶端就用到了這種控制項。 但是在android中並沒有現成的控制項可用,不過andro ...
  • 經過上一期的破解教程,相信大家跟我一樣都是對破解是初入門,我們破解的目的是什麼? 賺錢嗎?百度上一大堆破解版的應用,破解的人有賺到錢嗎?實實在在的說,其實也是方便自己而已。 玩個游戲,感覺過不去了,來個破解,無限金幣什麼的,通關輕輕鬆松是不?我們也在破解當中賺錢了不是?省去了買金幣的錢 感覺廢話說多 ...
  • 一、JDK配置 Android是基於Java進行開發的,首先需要在電腦上配置JDK(Java Development Kit)。在http://www.androiddevtools.cn/下載對應系統版本的JDK安裝包。 在Windows系統下雙擊下載的安裝包,一路下一步進行安裝(預設安裝路徑即可 ...
  • SpannableString其實和String一樣,都是一種字元串類型,SpannableString可以直接作為TextView的顯示文本,不同的是SpannableString可以通過使用其方法setSpan方法實現字元串各種形式風格的顯示,重要的是可以指定設置的區間,也就是為字元串指定下標區 ...
  • [1]獎金計算 [2]策略模式 [3]緩動動畫 [4]表單校驗 [5]總結 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...