javascript設(shè)計(jì)模式第四章惰性單例案例

通用的惰性單例一

假設(shè)我們是WebQQ 的開(kāi)發(fā)人員(網(wǎng)址是web.qq.com),當(dāng)點(diǎn)擊左邊導(dǎo)航里QQ 頭像時(shí),會(huì)彈出一個(gè)登錄浮窗(如圖4-1 所示),很明顯這個(gè)浮窗在頁(yè)面里總是唯一的,不可能出現(xiàn)同時(shí)存在兩個(gè)登錄窗口的情況。



第一種解決方案是在頁(yè)面加載完成的時(shí)候便創(chuàng)建好這個(gè)div 浮窗,這個(gè)浮窗一開(kāi)始肯定是隱藏狀態(tài)的,當(dāng)用戶點(diǎn)擊登錄按鈕的時(shí)候,它才開(kāi)始顯示:

//html
<button id="loginBtn">登錄</button>
//js
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';
  };

這種方式有一個(gè)問(wèn)題,也許我們進(jìn)入WebQQ 只是玩玩游戲或者看看天氣,根本不需要進(jìn)行登錄操作,因?yàn)榈卿浉〈翱偸且婚_(kāi)始就被創(chuàng)建好,那么很有可能將白白浪費(fèi)一些DOM節(jié)點(diǎn)。現(xiàn)在改寫一下代碼,使用戶點(diǎn)擊登錄按鈕的時(shí)候才開(kāi)始創(chuàng)建該浮窗:

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';
  };

雖然現(xiàn)在達(dá)到了惰性的目的,但失去了單例的效果。當(dāng)我們每次點(diǎn)擊登錄按鈕的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的登錄浮窗div。雖然我們可以在點(diǎn)擊浮窗上的關(guān)閉按鈕時(shí)(此處未實(shí)現(xiàn))把這個(gè)浮窗從頁(yè)面中刪除掉,但這樣頻繁地創(chuàng)建和刪除節(jié)點(diǎn)明顯是不合理的,也是不必要的。也許讀者已經(jīng)想到了,我們可以用一個(gè)變量來(lái)判斷是否已經(jīng)創(chuàng)建過(guò)登錄浮窗,這也是本節(jié)第一段代碼中的做法:

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';
  };

但是我們發(fā)現(xiàn)它還有如下一些問(wèn)題。

  1. 這段代碼仍然是違反單一職責(zé)原則的,創(chuàng)建對(duì)象和管理單例的邏輯都放在createLoginLayer
    對(duì)象內(nèi)部。
  2. 如果我們下次需要?jiǎng)?chuàng)建頁(yè)面中唯一的iframe,或者script 標(biāo)簽,用來(lái)跨域請(qǐng)求數(shù)據(jù),就
    必須得如法炮制,把createLoginLayer 函數(shù)幾乎照抄一遍:
 var createIframe = (function () {
    var iframe;
    return function () {
      if (!iframe) {
        iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        document.body.appendChild(iframe);
      }
      return iframe;
    }
  })();

我們需要把不變的部分隔離出來(lái),先不考慮創(chuàng)建一個(gè)div 和創(chuàng)建一個(gè)iframe 有多少差異,管理單例的邏輯其實(shí)是完全可以抽象出來(lái)的,這個(gè)邏輯始終是一樣的:用一個(gè)變量來(lái)標(biāo)志是否創(chuàng)建過(guò)對(duì)象,如果是,則在下次直接返回這個(gè)已經(jīng)創(chuàng)建好的對(duì)象:這些邏輯被封裝在getSingle函數(shù)內(nèi)部,創(chuàng)建對(duì)象的方法fn 被當(dāng)成參數(shù)動(dòng)態(tài)傳入getSingle 函數(shù):

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

接下來(lái)將用于創(chuàng)建登錄浮窗的方法用參數(shù)fn 的形式傳入getSingle,我們不僅可以傳入createLoginLayer,還能傳入createScript、createIframe、createXhr 等。之后再讓getSingle 返回一個(gè)新的函數(shù),并且用一個(gè)變量result 來(lái)保存fn 的計(jì)算結(jié)果。result 變量因?yàn)樯碓陂]包中,它永遠(yuǎn)不會(huì)被銷毀。在將來(lái)的請(qǐng)求中,如果result 已經(jīng)被賦值,那么它將返回這個(gè)值。代碼如下:

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';
  };

在這個(gè)例子中,我們把創(chuàng)建實(shí)例對(duì)象的職責(zé)和管理單例的職責(zé)分別放置在兩個(gè)方法里,這兩個(gè)方法可以獨(dú)立變化而互不影響,當(dāng)它們連接在一起的時(shí)候,就完成了創(chuàng)建唯一實(shí)例對(duì)象的功能,看起來(lái)是一件挺奇妙的事情。

通用的惰性單例二

這種單例模式的用途遠(yuǎn)不止創(chuàng)建對(duì)象,比如我們通常渲染完頁(yè)面中的一個(gè)列表之后,接下來(lái)要給這個(gè)列表綁定click 事件,如果是通過(guò)ajax 動(dòng)態(tài)往列表里追加數(shù)據(jù),在使用事件代理的前提下,click 事件實(shí)際上只需要在第一次渲染列表的時(shí)候被綁定一次,但是我們不想去判斷當(dāng)前是否是第一次渲染列表,如果借助于jQuery,我們通常選擇給節(jié)點(diǎn)綁定one 事件:

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

如果利用getSingle 函數(shù),也能達(dá)到一樣的效果。代碼如下:

var getSingle = function (fn) {
    var result;
    return function () {
      return result ? result : result = fn.apply(this, arguments);
    }
  }
  var bindEvent = getSingle(function () {
    document.getElementById('loginBtn').addEventListener("click", function () {
      alert('click');
    });

    return true;
  });
  var render = function () {
    console.log('開(kāi)始渲染列表');
    bindEvent();
  };
  render();
  render();
  render();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 8,131評(píng)論 2 17
  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,210評(píng)論 3 119
  • 在參加30天寫作的那段時(shí)間,還特意寫過(guò)一篇文章記錄夢(mèng)見(jiàn)蛇的事情及對(duì)夢(mèng)的解讀。當(dāng)時(shí)還以為是因?yàn)樽约耗芰μ鯇?duì)近期生活...
    露影晨夕閱讀 407評(píng)論 0 0
  • 1.名單5個(gè) 2.客戶的轉(zhuǎn)型其實(shí)是很快的,不用非得等到出了效果再轉(zhuǎn)介紹發(fā)圈,要跟客戶深聊,挖掘出客戶最關(guān)注的點(diǎn),然...
    甄程很自律閱讀 157評(píng)論 0 0
  • 行無(wú)障礙因,則得無(wú)障礙果報(bào)。如布施無(wú)障礙,謂不以能施之我(他如果不知道是我給的怎么辦)障礙,不以所施之物(多自留少...
    簡(jiǎn)佛系閱讀 703評(píng)論 1 6

友情鏈接更多精彩內(nèi)容