javascript單例、代理、狀態設計模式

1、建立型設計模式(三大類設計模式)

建立型設計模式 --"建立"說明該類別裏面的設計模式就是用來建立對象的,也就是在不一樣的場景下咱們應該選用什麼樣的方式來建立對象。javascript

1. 單例模式

==單例模式(Singleton)==:確保一個類只有一個實例,提供一個全局訪問點。html

  • ==標準==單例實現
    一般要實現一個單例模式並不複雜,無非就是用一個變量來標記當前是否已經爲了某個類建立過對象了,若是建立過對象了,則在下一次獲取該類的實例時,直接返回以前建立的對象。
/*單例實現代碼以下:*/
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 xiaoyi = Singleton.getInstance('小一');
var xiaoer = Singleton.getInstance('小二');
alert( xiaoyi === xiaoer ); //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;
})();
//測試
var xiaoyi = Singleton.getInstance('小一');
var xiaoer = Singleton.getInstance('小二');
alert( xiaoyi === xiaoer ); //true

結論: 不管咱們建立的實例叫什麼他老是同一個實例,這種寫法實現的單例雖然較爲簡單,可是增長了類的「不透明性」,使用這必須知道這個是一個單例類。java

  • 透明單例

    (試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸浮窗,這個浮窗應該是惟一的,不管單機多少次按鈕,每一個浮窗只會被建立一次,那麼這個浮窗就能用單例模式來建立)

    如今咱們來實現一個「透明」的單例類,這樣用戶使用這個類就能像使用其餘類同樣。咱們將建立CreateDiv單例類,他的做用是負責在頁面建立惟一的div節點。
/*使用透明的單例類來建立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.innnerHTML = this.html;
    document.body.appendChild(div);
  };
  return CreateDiv;
})();
//測試
var xiaoyi = new CreateDiv('小一');
var xiaoer = new CreateDiv('小二');
alert( xiaoyi === xiaoer ); //true

結論上邊這個單例雖然「透明」了,可是爲了把instance封裝起來,咱們使用了自執行匿名函數和閉包,而且讓匿名函數返回真正的Singleton構造方法。增長了程序複雜度。
CreateDiv的構造函數實際上負責了兩件事情。第一:建立對象,執行初始話方法,第二:保證只有一個實例對象。違背了單一原則。(單一原則:一個類最好只負責一項功能。控制類的粒度)

==致使的結果==當咱們要使用這個類實例多個對象的時候,就必需要把控制建立惟一對象的那段去掉,這種修改會給咱們帶來沒必要要的煩惱。設計模式

  • javascript中的單例

關於前邊所提到的幾種單例模式的實現,更可能是接近於傳統面嚮對象語言中的實現,單例從類建立而來,在以類爲中心的語言中,這是一種很天然的作法。好比java中若是須要某個對象,就必須先定義一個類,對象老是從類中建立而來。

就javascript而言,它實際上是一門無類(class-free)語言,在javascript中,既然咱們只須要一個「惟一」的對象,爲何要先建立一個類?在javascript中常常把全局變量當成單例來使用。緩存

var a = {};

用這種形式建立對象a,a是獨一無二的。聲明於全局下,提供全局訪問點也是必然的。

這種作法缺陷:全局變量存在不少問題,容易形成命名空間污染。本身命名變量隨時可能被別人覆蓋掉了。【處理】做爲開發這咱們應該儘可能減小全局變量的使用,即便使用也要把它的污染降到最低。服務器

  • 幾種下降全局變量帶來的命名污染。
  1. 使用命名空間
var namespace = {
a: {},
b: {},
};
  1. 使用閉包封裝私有變量
var user = (function(){
  var _name = 'sven',
  _age = 29;
  return {
    getUserInfo: function() {
      return _name + '-' _age;
    }
  }
})()
  • 通用的惰性單例
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";
};
var CreteSingleIframe = getSingle(function() { //動態加載第三頁面
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  return iframe;
});
document.getElementById('loginBtn').onclick = function()  {
  var loginLaryer = createSingleIframe();
  loginLayer.src = 'http://baidu.com'
}

結論 以上咱們把建立實例對象的職責和管理單例的職責分別放置在兩個方法裏,這兩個方法能夠獨立變化而互不影響,而當他們鏈接在一塊兒,就完成了建立惟一實例對象的功能。
網絡

總結當需求實例惟1、命名空間時,就可使用單例模式。結合閉包特性,用途普遍。閉包

2、結構型設計模式

結構型設計模式 --關注於如何將類或者對象組合成更大的結構,以便在使用的時候更簡化。app

1. 代理模式

==代理模式(proxy)==:爲一個對象提供一個代用品或佔位符,以便控制對於它訪問。異步

  • 代理模式在生活中的場景

好比:每一個明星都有經紀人做爲代理。若是想請明星來辦一場商業演出,那麼只能聯繫它的經紀人。經紀人會把商業演出的細節和報酬都談好以後,再把合同交給明星簽名確認。

graph LR
客戶((圓))-->本體((圓))
graph LR
客戶((圓))-->代理((圓))-->本體((圓))

代理至關於本體的替身,替身對請求作出一些處理後再把請求給本體對象。

  • 故事場景

    康康喜歡上瑪麗亞,康康在二月十四想送一束花表白瑪麗亞,恰好她兩有共同的朋友michael,因此康康就叫micheal幫忙送花給瑪麗亞。
/*用代碼來寫送花場景,首先是不經過(代理)*/
var Flower = function() {};
var kangkang = { //類
  sendFlower: function(target) { //傳入送花目標
    var flower = new Flower(); //實例一朵花
    target.receiveFlower(flower); //花傳給maria
  }
};
var maria = { //類
  receiveFlower: function(flower) { //接受類
    console.log('收到花了' + flower)
  }
};
kangkang.sendFlower(maria);
/*代理送花代碼*/
var Flower = function() {};
var kangkang = {
  sendFlower: function(target){
    var flower = new Flower();
    target.receiveFlower(flower);
  }
};
var michael = {//michael爲代理
  receiverFlower: function(flower) {
    maria.receiveFlower(flower);
  }
};
var maria = {
  receiveFlower: function(flower) {
    console.log('收到花了' + flower)
  }
};
kangkang.sendFlower(michael);

顯然看起來這樣送花不是有病? 能直接送到手爲何非要轉別人的手送花呢。其實否則,當康康本身去送話,若是瑪麗亞心情號成功概率有60%,可是心情差成功接近0,而經過jane的話jane可能較爲了解瑪麗亞,選擇心情好的時候去送,這時候就事半功倍了。

/*心情好的時候送花*/
var Flower = function() {};
var kangkang = {
  sendFlower: function(target){
    var flower = new Flower();
    target.receiveFlower(flower);
  }
};
var jane = {//jane爲代理
  receiverFlower: function(flower) {
    maria.listenGoodMood(function(){ //心情轉好是後送花
      maria.receiveFlower(flower);
    })
  }
};
var maria = {
  receiveFlower: function(flower) {
    console.log('收到花了' + flower)
  },
  listenGoodMood: function(fn) {//一秒後心情轉好
    setTimeout(function() {
     if(true){fn()} fn();
    }, 1000);
  }
};
kangkang.sendFlower(jane);
  • 保護代理和虛擬代理

從上面的例子咱們能夠看出這兩種代理的影子。代理jane能夠幫助瑪麗亞過濾掉一些請求,好比說送花的人不是康康,這樣就直接能夠在代理jane中被拒絕掉了。這種代理叫作保護代理。

假設new Flower一束花有期限,過時會凋謝,那麼咱們能夠把new flower 交給代理jane去執行,代理jane會選擇在瑪麗亞心情好的時候再去買花然後送花。這種代理就叫虛擬代理。(虛擬代理是把一些開銷很大的對象,延遲到真正須要它的時候再去建立。)

/*虛擬代理*/
var jane = {//jane爲代理
  receiverFlower: function(flower) {
    maria.listenGoodMood(function(){ //心情轉好是後送花
      var flower = new Flower(); //延遲建立 flower 對象
      maria.receiveFlower(flower);
    })
  }
};
  • 虛擬代理實現圖片的預加載

若是直接給某個img設置src屬性的話,可能會因爲網絡太差,圖片的位置每每有段時間會是一片空白,常見的做法就是用一張loading圖片佔位,而後用異步的方式加載圖片,等圖片加載好了再把它填充把img節點中。這種場景就用虛擬代理。

/*建立一個圖片元素而且提供一個設置s'r'c的接口*/
var myImage = (function() {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
  setSrc: function(src) {
    imgNode.src = src;
  }
}
})();
/*引入代理ProxyImg對象,在圖片真正被加載好以前用一張展位的菊花圖來提示用戶正在加載。*/
var proxyImage = (function(){
  var img = new Image;
  img.onload = function() { //當真正的圖加載完了在把圖片塞給他
    myImage.setSrc(this.src)
  }
  return {
    setSrc: function(src) { //一開始先設置一張菊花圖給他
      myImage.setSrc('./loading.gif');
      img.src = src;
    }
  }
})();
proxyImage.setSrc('http://wangshangtupian.jpg'); //模擬的圖片
  • 代理的意義

單一職責原則就一個類而言,應僅有一個引發它變化的緣由。

開放-封閉閉原則不用改變MyImage類或者添加接口,經過代理給系統添加了新的行爲,這符合開閉原則。

結論當咱們不須要預加載的功能,咱們只須要直接請求本體,而不須要請求代理,這就使用代理的靈活之處。

  • 虛擬代理合並HTTP請求

    先想象一下這樣一個場景:假設一個公司須要員工寫週報,週報要交給總監批閱,總監手下有150個員工,若是咱們每一個人把週報發給總監,那總監可能就不用工做了,每週看週報。若是將週報發給組長,組長整理後再發給總監,那總監就輕鬆多了。
/*須要作一個文件同步的功能,當點擊checkbox同時往另外一臺服務器同步文件,若是一秒點四個checkbox,那麼網絡開銷會至關大。咱們能夠經過一個代理函數來收集一段時間內的請求,最後一次性發給服務器請求*/
<body>
<input  type="checkbox" id="1"/>1
<input  type="checkbox" id="2"/>2
<input  type="checkbox" id="3"/>3
<input  type="checkbox" id="4"/>4
<input  type="checkbox" id="5"/>5
</body>

//給checkout 綁定事件
var synchronousFile = function(id) {
  console.log('開始同步文件, id爲:' + id);
};

var ProxySynchronousFile = (function(){
  var cache = [],//保存一段事件內須要同步的id
  timer; //定時器
  return function (id){
    cache.push(id);
    if(timer){ //保證不會覆蓋已經啓動的定時器
      return;
    }
    timer = setTimeout(function() {
      synchronousFile(cache.join(',')); //2秒後向服務器發送id的集合
      clearTimeout(timer);
      timer = null;
      cache.length = 0; //清空ID集合 
    }, 2000);
  }
})();

var checkbox = document.getElementsByTagName('input');
for (var i = 0, c; c= checkbox[i++];){
  c.onclick = function() {
    if(this.checked === true){
      ProxySynchronousFile(this.id);
    }
  }
}
  • 緩存代理

緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前一致,則能夠直接傳出以前的存儲的運算結果。

/*計算乘積*/
var mult = function() {
  console.log('開始計算乘積');
  var a = 1;
  for (var i = 0, l = arguments.length; i < l; i++){
    a = a * arguments[i];
  }
  return a;
}
var proxyMult = (function(){
  var cache = {};
  return function() {
    var args = Array.prototype.join.call(arguments, ',');
    if(args in cache){
      return cache[args];
    }
    return cache[args] = mult.apply(this, arguments);
  }
})();
proxyMult(1, 2, 3, 4); //24
proxyMult(1, 2, 3, 4); //24

總結代理模式應用普遍,好比說防火牆代理:控制網絡資源的訪問,保護主機不讓壞人破壞。遠程代理:爲一個對象在不一樣的地址空間提供局部表明。保護代理:用於對象應該有不一樣訪問權限狀況。智能引用代理:取代了簡單的指針,他在訪問對象是執行一些附加操做,好比計算一個對象被應用的次數。寫時複製代理:一般用於複製一個龐大的對象的狀況。寫時複製代理延遲了複製的過程。當對象真正被修改時,纔對它進行復制操做。寫時複製代理時虛擬代理的一種變體。更多就再也不一一贅述了。

3、行爲型設計模式

行爲型設計模式 --不僅僅涉及到類和對象,更關注於類或者對象之間的通信交流。

1. 狀態模式

==狀態模式(State Pattern)==:關鍵是區分事物內部的狀態,事物內部的狀態改變每每會帶來事物行爲的改變。

  • 初識狀態模式

想象一下:有一個電燈,只有一個控制電燈的開關。當電燈開着的時候按下開關,電燈會切換到關閉狀態;再按一次開關,電燈將又被打開,同一個開關在不一樣的狀態下表現出來的行爲是不同的。

var Light = function() {
  this.state = 'off'; //電燈初始狀態
  this.button = null; //電燈開關
}

Light.prototype.init = function() {
  var button = document.createElement('button').
  self = this;
  button.innerHTML = '開關';
  this.button = document.body.appendChild(button);
  this.button.onclick = function() {
    self.buttonWasPressed();//開關按下的行爲函數
  }
};
Ligth.prototype.buttonWasPressed = function() {
  if (this.state === 'off') {
    console.log('開燈');
    this.state = 'on';
  } else if (this.state === 'on') {
    console.log('關燈');
    this.state = 'off';
  }
};
var light = new Light();
light.init();

使人遺憾的是這個世界上的電燈並非只有這一種。某個酒店的燈,按一下開關弱光,在按一下開關強光,再按一下開光關閉。

Ligth.prototype.buttonWasPressed = function() {
  if (this.state === 'off') {
    console.log('弱光');
    this.state = 'ruoguang';
  } else if (this.state === 'ruoguang') {
    console.log('強光');
    this.state = 'qiangguang';
  } else if (this.state === 'qiangguang'){
    console.log('關燈');
    this.state = 'off';
  }
};

這個buttonWasPressed很明顯違反了開放-閉合原則,每次新增或修改電燈的狀態都須要重新改動buttonWasPressed方法中的代碼,這讓它成爲一個很是不穩定的方法。

/*讓咱們改進一下*/
var  OffLightState = function(light){
  this.light = light;
}
OffLightState.prototype.buttonWasPressed = function() {
  console.log('弱光');
  this.light.setState(this.light.weakLightState);
}

var  WeakLightState = function(light){
  this.light = light;
}
WeakLightState.prototype.buttonWasPressed = function() {
  console.log('強光');
  this.light.setState(this.light.strongLightState);
}

var  strongLightState = function(light){
  this.light = light;
}
strongLightState.prototype.buttonWasPressed = function() {
  console.log('關燈');
  this.light.setState(this.light.OffLightState);
}
//改寫類
var Light = function() {
  this.OffLightState = new OffLightState(this);
  this.WeakLightState = new WeakLightState(this);
  this.strongLightState = new strongLightState(this);
  this.button = null;
};

Light.prototype.init = function(){
  var button = document.createElement('button'),
  self = this;
  this.button = document.body.appendChild(button);
  this.button.innerHTML = '開關';
  this.currState = this.offLightState; //設置當前狀態
  this.button.onclick = function(){
    self.currState.buttonWasPressed();
  }
}

Light.prototype.setState = function(newState){
  this.currState = newState;
};
var light = new Light();
light.init();

總結狀態模式的優缺點

  1. 狀態模式定義了狀態與行爲之間的關係,並將他們封裝在一個類裏,經過增長新的狀態,很容易增長新的狀態和轉換。
  2. 避免Context無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了context本來過多的條件分支。
  3. 用對象代替字符串來記錄當前的狀態,使得狀態的切換更加一目瞭然。
  4. context中的請求動做和狀態類中封裝的行爲能夠很是容易的獨立變化而不互相影響。
  5. 缺點:會在系統中定義許多的狀態類,編寫二十個狀態類是一項枯燥乏味的工做,並且系統中會所以增長很多對象,另外因爲邏輯分佈在狀態類中,雖然避開了不受歡迎的條件語句,可是也形成了邏輯分散的問題。咱們沒法在一個地方就看出整個狀態機的邏輯。
相關文章
相關標籤/搜索