js設計模式學習之單例模式

單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點html

實現一個單例模式

用一個變量標誌當前是否已經爲某個類型建立過對象,若是是,則下次直接返回以前建立的對象。設計模式

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

Singleton.prototype.getName = function () {
  console.log(this.name);
}

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

var a = Singleton.getInstance('Tony1');
var b = Singleton.getInstance('Tony2');

console.log(a === b); // true
複製代碼

經過 Singleton.getInstance來獲取 Singleton 類的惟一對象,裏邊使用了 new 來獲取,致使了這個類的「不透明性」。閉包

透明的單例模式

建立一個「透明」的單例類,就是讓咱們從這個類中建立對象的時候能夠和使用其餘普通類同樣:var aa = new CreateDiv('Sisi1');app

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 aa = new CreateDiv('Sisi1');
var bb = new CreateDiv('Sisi2');

console.log(aa === bb);  // true
複製代碼

下面這段代碼中,CreateDiv 的構造函數負責了兩件事:建立對象和執行初始化 init 方法,及保證只有一個對象:dom

var CreateDiv = function (html) {
  if (instance) {
    return instance;
  }
  this.html = html;
  this.init();
  return instance = this;
};
複製代碼

可是,若是咱們要建立不少的div,這裏的 return instance = this; 就須要刪掉。函數

用代理實現單例模式

這時候,爲了不上面不能複用的尷尬,經過引入代理類的方式,把負責管理單例的邏輯移交至代理類ProxySingletonCreateDiv,這樣CreateDiv只是一個普通的類。學習

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

var ProxySingletonCreateDiv = (function () {
  var instance;
  return function (html) {
    if (!instance) {
      instance = new CreateDiv(html);
    }
    return instance;
  }
})();

var aa = new ProxySingletonCreateDiv('Tony1');
var bb = new ProxySingletonCreateDiv('Tony2');

console.log(aa === bb); // true
複製代碼

JavaScript 中的單例模式

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

  1. 使用命名空間

對象字面量的方式:this

var namespace1 = {
  a: function() {
    console.log(1);
  },
  b: function() {
    console.log(2);
  }
}
namespace1.a(); //1
複製代碼

把a和b都定義爲 namespace1 的屬性,減小了變量和全局做用域打交道的機會,還能夠動態地建立命名空間:spa

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.log(MyApp);

// 至關於:
var MyApp = {
  event: {},
  dom: {
    style: {}
  }
}
複製代碼
  1. 使用閉包封裝私有變量

使用下劃線約定私有變量 _name 和 _age。

var user = (function () {
  var _name = 'Seven';
  var _age = 27;

  return {
    getUserInfo: function () {
      return _name + '-' + _age;
    }
  }
})();

console.log(user.getUserInfo()) // Seven-27
複製代碼

惰性單例

宗旨:在須要的時候才建立對象!!!

栗子:QQ的登陸浮窗

第一種方案:頁面加載完成的時候便建立好浮窗。

var loginLayer = (function () {
  var div = document.createElement('div');
  div.innerHTML = '我是一個小小的懸浮框';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
})();

document.getElementById('loginBtn').addEventListener('click', function () {
  loginLayer.style.display = 'block';
});
複製代碼

可是,無論咱們登陸與否,都會建立懸浮窗,因此咱們能夠修改成:在點擊登陸的時候再建立懸浮窗。

var createLoginLayer = function () {
  var div = document.createElement('div');
  div.innerHTML = '我是一個小小的懸浮框';
  div.style.display = 'none';
  document.body.appendChild(div);
  return div;
};

document.getElementById('loginBtn').addEventListener('click', function () {
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
});
複製代碼

這時候,雖然達到了惰性的目的,卻失去了單例的效果,每次點擊登陸,都會建立一個新的懸浮窗。

因此咱們須要一個變量來判斷是否已經建立過懸浮窗:

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').addEventListener('click', function () {
  var loginLayer = createLoginLayer();
  loginLayer.style.display = 'block';
});
複製代碼

通用的惰性單例

雖然上面的懸浮框是一個可用的惰性單例,可是仍然違反了單一職責原則,若是咱們要建立其餘的標籤,就須要把建立懸浮窗的函數複製一份,再修修改改,沒法作到複用。

因此,咱們須要把不變的部分隔離出來,進行抽象,不管建立什麼標籤,都是同樣的邏輯:

var obj;
if(!obj) {
  obj = xxx;
}
複製代碼

接着,繼續:

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').addEventListener('click', function () {
  var loginLayer = createSingleLoginLayer();
  loginLayer.style.display = 'block';
});
複製代碼

這時,咱們建立其餘標籤就只須要關係如何建立該標籤就能夠:

var createIframe = function () {
  var iframe = document.createElement('iframe');
  iframe.src = 'https://baidu.com';
  document.body.appendChild(iframe);
  return iframe;
}

var createSingleIframe = getSingle(createIframe);

document.getElementById('loginBtn2').addEventListener('click', function () {
  createSingleIframe();
});
複製代碼

小結

單例模式是一種簡單卻很是經常使用的模式,特別是惰性單例技術,在合適的時候才建立對象,而且只建立惟一的一個。

建立對象管理單例 的職責被分佈在兩個不一樣的方法中,兩個方法組合起來才具備單例模式的威力。

學習資料:

  • 《JavaScript 設計模式與開發實踐》第 4 章
相關文章
相關標籤/搜索