欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript設計模式之單例模式

 更新時間:2022年08月09日 14:22:59   作者:夏安  
單例模式的定義是:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

單例模式

單例模式是一種常用的模式,有一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽器中的 window 對象等。在 JavaScript 開發(fā)中,單例模式的用途同樣非常廣泛。試想一下,當我 們單擊登錄按鈕的時候,頁面中會出現(xiàn)一個登錄浮窗,而這個登錄浮窗是唯一的,無論單擊多少 次登錄按鈕,這個浮窗都只會被創(chuàng)建一次,那么這個登錄浮窗就適合用單例模式來創(chuàng)建。

實現(xiàn)單例模式

要實現(xiàn)一個標準的單例模式并不復雜,無非是用一個變量來標志當前是否已經(jīng)為某個類創(chuàng)建 過對象,如果是,則在下一次獲取該類的實例時,直接返回之前創(chuàng)建的對象。代碼如下:

class Singleton {
  constructor(name) {
    this.name = name;
    this.instance = null;
  }
  static getInstance(name) {
    if (this.instance === null) {
      this.instance = new Singleton(name);
    }
    return this.instance;
  }
}

我們通過 Singleton.getInstance 來獲取 Singleton 類的唯一對象,這種方式相對簡單,但有 一個問題,就是增加了這個類的“不透明性”,Singleton 類的使用者必須知道這是一個單例類, 跟以往通過 new XXX 的方式來獲取對象不同,這里偏要使用 Singleton.getInstance 來獲取對象。 接下來順便進行一些小測試,來證明這個單例類是可以信賴的:

const a = Singleton.getInstance( '夏安1' ); 
const b = Singleton.getInstance( '夏安2' ); 
console.log(a === b); // true

雖然現(xiàn)在已經(jīng)完成了一個單例模式的編寫,但這段單例模式代碼的意義并不大。從下一節(jié)開 始,我們將一步步編寫出更好的單例模式。

透明的單例模式

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

class CreateDiv {
  constructor(html) {
    if (!CreateDiv.instance) {
      const div = document.createElement('div');
      div.innerHTML = html;
      document.body.appendChild(div);
      CreateDiv.instance = div;
    }
    return CreateDiv.instance;
  }
  static instance = null;
}

然而,假設我們某天需要利用這個類,在頁面中創(chuàng)建千千萬萬的 div,即要讓這個類從單例類變成 一個普通的可產(chǎn)生多個實例的類,那我們必須得改寫 CreateDiv 構(gòu)造函數(shù),把控制創(chuàng)建唯一對象的那一個靜態(tài)屬性去掉,這種修改會給我們帶來不必要的煩惱。

用代理實現(xiàn)單例模式

現(xiàn)在我們通過引入代理類的方式,來解決上面提到的問題。 我們依然使用上一節(jié)節(jié)中的代碼,首先 CreateDiv 構(gòu)造函數(shù)中,把負責管理單例的代碼移除 出去,使它成為一個普通的創(chuàng)建 div 的類:

class CreateDiv {
  constructor(html) {
    const div = document.createElement('div');
    div.innerHTML = html;
    document.body.appendChild(div);
    return div;
  }
}
class ProxySingletonCreateDiv {
  constructor(html) {
    if (!CreateDiv.instance) {
      CreateDiv.instance = new CreateDiv(html);
    }
    return CreateDiv.instance;
  }
  static instance = null;
}

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

本例是緩存代理的應用之一,之后我們將繼續(xù)了解代理帶來的好處。

惰性單例

前面我們了解了單例模式的一些實現(xiàn)辦法,本節(jié)我們來了解惰性單例。

惰性單例指的是在需要的時候才創(chuàng)建對象實例。惰性單例是單例模式的重點,這種技術(shù)在實際開發(fā)中非常有用,有用的程度可能超出了我們的想象,實際上在本文開頭就使用過這種技術(shù), instance 實例對象總是在我們調(diào)用 Singleton.getInstance 的時候才被創(chuàng)建,而不是在頁面加載好的時候就創(chuàng)建,代碼如下:

static getInstance(name) {
    if (this.instance === null) {
        this.instance = new Singleton(name);
    }
    return this.instance;
}

不過這是基于“類”的單例模式,下面我們將以 WebQQ 的登錄浮窗為例,介紹與全局變量結(jié)合實現(xiàn)惰性的單例。

假設我們是 WebQQ 的開發(fā)人員,當點擊左邊導航里 QQ 頭像時,會彈出一個登錄浮窗,很明顯這個浮窗在頁面里總是唯一的,不可能出現(xiàn)同時存在 兩個登錄窗口的情況。

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>惰性單例</title>
</head>
<body>
  <button id='login-button'>登錄</button>
</body>
<script>
  var loginLayer = (function(){ 
    var div = document.createElement('div'); 
    div.innerHTML = '我是登錄浮窗'; 
    div.style.display = 'none'; 
    document.body.appendChild(div); 
    return div; 
  })(); 
  document.getElementById('login-button').onclick = function() {
    loginLayer.style.display = 'block';
  }
</script>
</html>

這種方式有一個問題,也許我們進入 WebQQ 只是玩玩游戲或者看看天氣等,根本不需要進行登錄操作,因為登錄浮窗總是一開始就被創(chuàng)建好,那么很有可能將白白浪費一些 DOM 節(jié)點。 現(xiàn)在改寫一下代碼,使用戶點擊登錄按鈕的時候才開始創(chuàng)建該浮窗:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>惰性單例</title>
</head>
<body>
  <button id='login-button'>登錄</button>
</body>
<script>
  var createLoginLayer = function(){ 
    var div = document.createElement('div'); 
    div.innerHTML = '我是登錄浮窗'; 
    div.style.display = 'none'; 
    document.body.appendChild(div); 
    return div; 
  }; 
  document.getElementById('login-button').onclick = function() {
    var loginLayer = createLoginLayer(); 
    loginLayer.style.display = 'block';
  }
</script>
</html>

雖然現(xiàn)在達到了惰性的目的,但失去了單例的效果。當我們每次點擊登錄按鈕的時候,都會 創(chuàng)建一個新的登錄浮窗 div。雖然我們可以在點擊浮窗上的關(guān)閉按鈕時(此處未實現(xiàn))把這個浮 窗從頁面中刪除掉,但這樣頻繁地創(chuàng)建和刪除節(jié)點明顯是不合理的,也是不必要的。

也許讀者已經(jīng)想到了,我們可以用一個變量來判斷是否已經(jīng)創(chuàng)建過登錄浮窗,這也是本節(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('login-button').onclick = function() {
  var loginLayer = createLoginLayer(); 
  loginLayer.style.display = 'block';
}

通用的惰性單例

上一節(jié)我們完成了一個可用的惰性單例,但是我們發(fā)現(xiàn)它還有如下一些問題。

這段代碼仍然是違反單一職責原則的,創(chuàng)建對象和管理單例的邏輯都放在 createLoginLayer 對象內(nèi)部。

如果我們下次需要創(chuàng)建頁面中唯一的 iframe,或者 script 標簽,用來跨域請求數(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; 
  }
})(); 

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

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

現(xiàn)在我們就把如何管理單例的邏輯從原來的代碼中抽離出來,這些邏輯被封裝在 getSingle 函數(shù)內(nèi)部,創(chuàng)建對象的方法 fn 被當成參數(shù)動態(tài)傳入 getSingle 函數(shù):

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

接下來將用于創(chuàng)建登錄浮窗的方法用參數(shù) fn 的形式傳入 getSingle,我們不僅可以傳入 createLoginLayer,還能傳入 createScriptcreateIframe、createXhr 等。之后再讓 getSingle 返回 一個新的函數(shù),并且用一個變量 result 來保存 fn 的計算結(jié)果。result 變量因為身在閉包中,它永遠不會被銷毀。在將來的請求中,如果 result 已經(jīng)被賦值,那么它將返回這個值。代碼如下:

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

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

小結(jié)

單例模式是我們學習的第一個模式,我們先學習了傳統(tǒng)的單例模式實現(xiàn),也了解到因為語言的差異性,有更適合的方法在 JavaScript 中創(chuàng)建單例。本文還提到了代理模式和單一職責原則, 后面的章節(jié)會對它們進行更詳細的講解。

getSinge 函數(shù)中,實際上也提到了閉包和高階函數(shù)的概念。單例模式是一種簡單但非常實 用的模式,特別是惰性單例技術(shù),在合適的時候才創(chuàng)建對象,并且只創(chuàng)建唯一的一個。更奇妙的 是,創(chuàng)建對象和管理單例的職責被分布在兩個不同的方法中,這兩個方法組合起來才具有單例模式的威力。

到此這篇關(guān)于JavaScript單例模式的文章就介紹到這了,更多相關(guān)JS單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論