js設計模式--單例模式

前言

本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些本身的思考。但願對你們有所幫助。html

文章系列

js設計模式--單例模式es6

js設計模式--策略模式segmentfault

js設計模式--代理模式設計模式

概念

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

UML類圖

clipboard.png

場景

單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏 覽器中的 window 對象等。閉包

在 JavaScript 開發中,單例模式的用途一樣很是普遍。試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸浮窗,而這個登陸浮窗是惟一的,不管單擊多少 次登陸按鈕,這個浮窗都只會被建立一次,那麼這個登陸浮窗就適合用單例模式來建立。app

優缺點

優勢:建立對象和管理單例的職責被分佈在兩個不一樣的方法中dom

實現

1. 咱們的第一個單例

var instance = null
var getInstance = function(arg) {
  if (!instance) {
    instance = arg
  }
  return instance
}

var a = getInstance('a')
var b = getInstance('b')
console.log(a===b)

這種定義一個全局變量的方式很是不優雅,也很差複用代碼函數

2. 利用閉包實現單例

var Singleton = function( name ){
  this.name = name;
};

Singleton.getInstance = (function(){
  var instance = null;
  return function( name ){
    if ( !instance ){
      instance = new Singleton( name );
    }
    return instance;
  }
})();
var a = Singleton.getInstance('a')
var b = Singleton.getInstance('b')
console.log(a===b)

有些同窗可能對閉包不大理解,下面用函數實現一下this

3. 利用函數實現單例

function Singleton(name) {
  this.name = name
  this.instance = null
}

Singleton.getInstance = function(name) {
  if (!this.instance) {
    this.instance = new Singleton(name)
  }
  return this.instance
}

var a = Singleton.getInstance('a')
var b = Singleton.getInstance('b')
console.log(a===b)

2,3這兩種方式也有缺點,就是咱們必須調用getInstance來建立對象,通常咱們建立對象都是利用new操做符

4. 透明的單例模式

var Singleton = (function() {
  var instance
  Singleton = function(name) {
    if (instance) return instance
    this.name = name
    return instance = this
  }
  return Singleton
})()

var a = new Singleton('a')
var b = new Singleton('b')
console.log(a===b)

這中方法也有點缺點:不符合單一職責原則,這個對象其實負責了兩個功能:單例和建立對象

下面咱們分離這兩個職責

5. 利用代理實現單例

var People = function(name) {
  this.name = name
}

var Singleton = (function() {
  var instance
  Singleton = function(name) {
    if (instance) return instance
    return instance = new People(name)
  }
  return Singleton
})()

var a = new Singleton('a')
var b = new Singleton('b')
console.log(a===b)

這中方法也有點缺點:代碼不能複用。若是咱們有另一個對象也要利用單例模式,那咱們不得不寫重複的代碼

6. 提供通用的單例

var People = function(name) {
  this.name = name
}

var Singleton = function(Obj) {
  var instance
  Singleton = function() {
    if (instance) return instance
    return instance = new Obj(arguments)
  }
  return Singleton
}

var peopleSingleton = Singleton(People)

var a = new peopleSingleton('a')
var b = new peopleSingleton('b')
console.log(a===b)

到這裏已經比較完美了,等等這只是es5的寫法,下面咱們用es6來實現一下

7. es6單例模式

class People {
    constructor(name) {
        if (typeof People.instance === 'object') {
            return People.instance;
        }
        People.instance = this;
        this.name = name
        return this;
    }
}
var a = new People('a')
var b = new People('b')
console.log(a===b)

比較以上幾種實現

  1. 用全局變量的第1種方法,應該摒棄
  2. 用閉包實現的第2種方式,instance 實例對象老是在咱們調用 Singleton.getInstance 的時候才被建立,應該摒棄
  3. 其餘方式都是惰性單例(在須要時才建立)

js的特殊性

咱們都知道:JavaScript 實際上是一門無類(class-free)語言,,生搬單例模式的概念並沒有意義。

單例模式的核心是確保只有一個實例,並提供全局訪問。

咱們能夠用一下幾種方式來另類實現

1. 全局變量

好比var a = {},這時全局就只有一個a對象
但全局變量存在不少問題,它很容易形成命名空間污染,咱們用如下兩種方式解決

2.使用命名空間

var namespace1 = {
    a: function () {
      alert(1);
    },
    b: function () {
      alert(2);
    }
  };

另外咱們還能夠動態建立命名空間

var MyApp = {};
  MyApp.namespace = function (name) {
    var parts = name.split('.');
    var current = MyApp;
    for (var i in parts) {
      if (!current[parts[i]]) {
        current[parts[i]] = {};
      }
      current = current[parts[i]];
    }
  };
  MyApp.namespace('event');
  MyApp.namespace('dom.style');
  console.dir(MyApp);
  // 上述代碼等價於:
  var MyApp = {
    event: {},
    dom: {
      style: {}
    }
  };

3. 閉包

var user = (function () {
    var __name = 'sven',
      __age = 29;
    return {
      getUserInfo: function () {
        return __name + '-' + __age;
      }
    }
  })();

例子

登陸框

下面咱們來實現一個點擊登陸按鈕彈出登陸框的例子

粗糙的實現

<html>

<body>
  <button id="loginBtn">登陸</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('loginBtn').onclick = function () {

    loginLayer.style.display = 'block';
  };
</script>

</html>

上面這種方式若是用戶沒有點擊登陸按鈕,也會在一開始就建立登陸框

改進

<html>

<body>
  <button id="loginBtn">登陸</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('loginBtn').onclick = function () {
    var loginLayer = createLoginLayer();
    loginLayer.style.display = 'block';
  };
</script>

</html>

這種方式每次點擊按鈕都會建立一個登陸框

再改進

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

這種方式不夠通用,不符合單一職責原則

再再改進

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

  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 = 'http://baidu.com';
  };

至此已經完美

相關文章
相關標籤/搜索