javascript設(shè)計模式之定義及案例

高階函數(shù)是指至少滿足下列條件之一的函數(shù):

1) 函數(shù)可以作為參數(shù)被傳遞
2) 函數(shù)可以作為參數(shù)輸出贾漏。

AOP:

1)AOP(面向切面編程)的主要作用是把一些跟核心業(yè)務(wù)邏輯模塊無關(guān)的功能抽離出來液样,這些跟業(yè)務(wù)邏輯無關(guān)的功能通常包括日志統(tǒng)計棵譬、安全控制寥枝、異常處理等。把這些功能抽離出來之后侯嘀,再通過“動態(tài)織入”的方式摻入業(yè)務(wù)邏輯模塊中庆冕。這樣做的好處首先是可以保持業(yè)務(wù)邏輯模塊的純凈和高內(nèi)聚性,其次是可以很方便地復(fù)用日志統(tǒng)計等功能模塊实夹。

單一職責(zé)原則:

單一職責(zé)原則指的是橄浓,就一個類(通常也包括對象和函數(shù)等)而言,應(yīng)該僅有一個引起它變化的原因亮航。如果一個對象承擔(dān)了多項職責(zé)荸实,就意味著這個對象將變得巨大,引起它變化的原因可能會有多個缴淋。面向?qū)ο笤O(shè)計鼓勵將行為分布到細粒度的對象之中准给,如果一個對象承擔(dān)的職責(zé)過多泄朴,等于把這些職責(zé)耦合到了一起,這種耦合會導(dǎo)致脆弱和低內(nèi)聚的設(shè)計露氮。當(dāng)變化發(fā)生時祖灰,設(shè)計可能會遭到意外的破壞。

單例模式的定義是:

1)保證一個類僅有一個實例
2)并提供一個訪問它的全局訪問點

var a={}
//或者
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 a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true
<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>

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

<html>

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

1)這段代碼仍然是違反單一職責(zé)原則的沈跨,創(chuàng)建對象和管理單例的邏輯都放在createLoginLayer對象內(nèi)部。
2)如果我們下次需要創(chuàng)建頁面中唯一的iframe兔综,或者script 標(biāo)簽饿凛,用來跨域請求數(shù)據(jù),就必須得如法炮制软驰,把createLoginLayer 函數(shù)幾乎照抄一遍
現(xiàn)在我們就把如何管理單例的邏輯從原來的代碼中抽離出來涧窒,這些邏輯被封裝在getSingle函數(shù)內(nèi)部,創(chuàng)建對象的方法fn 被當(dāng)成參數(shù)動態(tài)傳入getSingle 函數(shù):

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

<body>
    <button id="loginBtn">登錄</button>
 <button id="loginBtnframe">登錄frame</button>
</body>
<script>
    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 createSingleIframe = getSingle(function () {
        var iframe = document.createElement('iframe');
        document.body.appendChild(iframe);
        return iframe;
    });
    document.getElementById('loginBtnframe').onclick = function () {
        var loginLayer = createSingleIframe();
        loginLayer.src = 'http://baidu.com';
    };
</script>

</html>

策略模式:

定義:定義一系列的算法锭亏,把它們一個個封裝起來纠吴,并且使它們可以相互替換。
策略模式是一種常用且有效的設(shè)計模式慧瘤,可以用于計算獎金戴已、緩動動畫、表單校驗等例子來加深大家對策略模式的理解锅减。
下面是策略模式的幾個有點:
1)策略模式利用組合糖儡、委托和多態(tài)等技術(shù)和思想,可以有效地避免多重條件選擇語句怔匣。
2) 策略模式提供了對開放—封閉原則的完美支持握联,將算法封裝在獨立的strategy 中,使得它們易于切換每瞒,易于理解金闽,易于擴展。
3) 策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方剿骨,從而避免許多重復(fù)的復(fù)制粘貼工作代芜。
4) 在策略模式中利用組合和委托來讓Context 擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的替代方案懦砂。

var strategies = {
  "S": function( salary ){
      return salary * 4;
  },
  "A": function( salary ){
      return salary * 3;
  },
  "B": function( salary ){
      return salary * 2;
  }
};
var calculateBonus = function( level, salary ){
  return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000

代理模式:

代理模式是為一個對象提供一個代用品或占位符蜒犯,以便控制對它的訪問组橄。代理模式是一種非常有意義的模式,在生活中可以找到很多代理模式的場景罚随。比如玉工,明星都有經(jīng)紀人作為代理。如果想請明星來辦一場商業(yè)演出淘菩,只能聯(lián)系他的經(jīng)紀人遵班。經(jīng)紀人會把商業(yè)演出的細節(jié)和報酬都談好之后,再把合同交給明星簽潮改。代理模式的關(guān)鍵是狭郑,當(dāng)客戶不方便直接訪問一個對象或者不滿足需要的時候,提供一個替身對象來控制對這個對象的訪問汇在,客戶實際上訪問的是替身對象翰萨。替身對象對請求做出一些處理之后,再把請求轉(zhuǎn)交給本體對象糕殉。
在JavaScript 開發(fā)中最常用的是虛擬代理和緩存代理:

//虛擬代理
var myImage = (function () {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
      setSrc: function (src) {
        imgNode.src = src;
      }
    }
  })();
  var proxyImage = (function () {
    var img = new Image;
    img.onload = function () {
      myImage.setSrc(this.src);
    }
    return {
      setSrc: function (src) {
        myImage.setSrc('file:// /C:/Users/svenzeng/Desktop/loading.gif');
        img.src = src;
      }
    }
  })();
  proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');
//緩存代理
var mult=function(){
  console.log("開始計算乘積");
  var a=1;
  for(var i=0;i<arguments.length;i++){
    a=a*arguments[i];
  }
  return a;
}
var plus=function(){
  console.log("開始計算乘積");
  var a=0;
  for(var i=0;i<arguments.length;i++){
    a+=arguments[i];
  }
  return a;
}
var proxyall=function(fn){
  var cache=[];
  return function(){
    var regs=[].join.call(arguments,",");
    if(cache[regs]){
      return cache[regs];
    }else{
      return cache[regs]=fn.apply(this,arguments);
    }
  }
};
var proxyMult=proxyall(mult);
var proxyPlus=proxyall(plus);
console.log(proxyMult(1,2,3,4));
console.log(proxyMult(1,2,3,4));
console.log(proxyPlus(1,2,3,4));
console.log(proxyPlus(1,2,3,4));

輸出結(jié)果:

開始計算乘積
24
24
開始計算乘積
10
10

迭代器模式:

指提供一種方法順序訪問一個聚合對象中的各個元素亩鬼,而又不需要暴露該對象的內(nèi)部表示。迭代器模式可以把迭代的過程從業(yè)務(wù)邏輯中分離出來阿蝶,在使用迭代器模式之后雳锋,即使不關(guān)心對象的內(nèi)部構(gòu)造,也可以按順序訪問其中的每個元素羡洁。

var each = function (ary, callback) {
    for (var i = 0, l = ary.length; i < l; i++) {
      if (callback(i, ary[i]) === false) { // callback 的執(zhí)行結(jié)果返回false玷过,提前終止迭代
        break;
      }
    }
  };
  each([1, 2, 3, 4, 5], function (i, n) {
    if (n > 3) { // n 大于3 的時候終止循環(huán)
      return false;
    }
    console.log(n); // 分別輸出:1, 2, 3
  });

輸出結(jié)果:

1
2
3

發(fā)布—訂閱模式

發(fā)布—訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關(guān)系筑煮,當(dāng)一個對象的狀態(tài)發(fā)生改變時辛蚊,所有依賴于它的對象都將得到通知。在JavaScript 開發(fā)中咆瘟,我們一般用事件模型來替代傳統(tǒng)的發(fā)布—訂閱模式嚼隘。

var Events = {
    clientList: {},
    listen: function (key, fn) {
      var fns = this.clientList[key]
      if (!fns) {
        fns = this.clientList[key] = [];
      }
      if (fn) {
        fns.push(fn);
      }
    },
    trigger: function () {
      var key = [].shift.call(arguments);
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = fns[i++];) {
        ary.apply(this, arguments);
      }
    },
    remove: function (key, fn) {
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      if (!fn) {
        fns.length = 0;
      } else {
        for (var i = 0; i < fns.length; i++) {
          if (fns[i] == fn) {
            fns.splice(i, 1);
          }
        }
      }
    }
  }
   var header = (function () { // header 模塊
    Events.listen('loginSucc', function (data) {
      header.setAvatar(data.avatar);
    });
    return {
      setAvatar: function (data) {
        console.log(data+'設(shè)置header 模塊的頭像');
      }
    }
  })();
  var nav = (function () { // nav 模塊
    Events.listen('loginSucc', function (data) {
      nav.setAvatar(data.avatar);
    });
    return {
      setAvatar: function (data) {
        console.log(data+'設(shè)置nav 模塊的頭像');
      }
    }
  })();
//用setTimeout模擬真實操作
  setTimeout(function(){
    Events.trigger('loginSucc',{avatar:"登錄成功開始"});
  },3000);
  //下面是真實項目請求后操作
  // $.ajax('http:// xxx.com?login', function (data) { // 登錄成功
    // login.trigger('loginSucc', data); // 發(fā)布登錄成功的消息
  // });

輸出結(jié)果:

登錄成功開始設(shè)置header 模塊的頭像
登錄成功開始設(shè)置nav 模塊的頭像
//也可以先發(fā)布诽里,后訂閱下面是簡單js部分
var Events = {
    clientList: {},
    cache: {},
    listen: function (key, fn) {
      var fns = this.clientList[key]
      if (!fns) {
        fns = this.clientList[key] = [];
      }
      if (fn) {
        fns.push(fn);
      }
      if (JSON.stringify(this.cache) != "{}") {
        this._trigger(key, fn);
        this.cache = [];
      };

    },
    _trigger: function (key, fn) {
      var arys = this.cache[key];
      if (!fn || fn.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = arys[i++];) {
        fn.apply(this, ary);
      }
    },
    trigger: function () {
      var key = [].shift.call(arguments);
      var fns = this.clientList[key];
      if (typeof ss != "function" || typeof ss != "undefined") {
        if (!this.cache[key]) {
          this.cache[key] = [];
        }
        this.cache[key].push(arguments);
      }
      if (!fns || fns.length == 0) {
        return false;
      }
      for (var i = 0, ary; ary = fns[i++];) {
        ary.apply(this, arguments);
      }
    },
    remove: function (key, fn) {
      var fns = this.clientList[key];
      if (!fns || fns.length == 0) {
        return false;
      }
      if (!fn) {
        fns.length = 0;
      } else {
        for (var i = 0; i < fns.length; i++) {
          if (fns[i] == fn) {
            fns.splice(i, 1);
          }
        }
      }
    }
  }
  Events.trigger('click', 1);
  Events.listen('click', function (a) {
    console.log(a); // 輸出:1
  });

輸出結(jié)果:

1

命令模式

命令模式的由來袒餐,其實是回調(diào)(callback)函數(shù)的一個面向?qū)ο蟮奶娲罚?code>命令模式在JavaScript 語言中是一種隱形的模式。
命令模式是最簡單和優(yōu)雅的模式之一谤狡,命令模式中的命令(command)指的是一個執(zhí)行某些特定事情的指令灸眼。命令模式最常見的應(yīng)用場景是:有時候需要向某些對象發(fā)送請求,但是并不知道請求的接收者是誰墓懂,也不知道被請求的操作是什么焰宣。此時希望用一種松耦合的方式來設(shè)計程序,使得請求發(fā)送者和請求接收者能夠消除彼此之間的耦合關(guān)系捕仔。
1)簡單的命令模式案例:

//html
<button id="button1">點擊按鈕1</button>
<button id="button2">點擊按鈕2</button>
<button id="button3">點擊按鈕3</button>
//不是命令模式實現(xiàn)
 var bindClick = function (button, func) {
    button.onclick = func;
  };
  var MenuBar = {
    refresh: function () {
      console.log('刷新菜單界面');
    }
  };
  var SubMenu = {
    add: function () {
      console.log('增加子菜單');
    },
    del: function () {
      console.log('刪除子菜單');
    }
  };
  bindClick(button1, MenuBar.refresh);
  bindClick(button2, SubMenu.add);
  bindClick(button3, SubMenu.del);
//命令模式實現(xiàn)
   var MenuBar = {
    refresh: function () {
      console.log('刷新菜單目錄');
    }
  };
  var SubMenu = {
    add: function () {
      console.log('增加子菜單');
    },
    del: function () {
      console.log('刪除子菜單');
    }
  };
  var setCommand = function (button, command) {
    button.onclick = function () {
      command.execute();
    }
  };
  var RefreshMenuBarCommand = function (receiver) {
    this.receiver = receiver;
  };
  RefreshMenuBarCommand.prototype.execute = function () {
    this.receiver.refresh();
  };
  var AddSubMenuCommand = function (receiver) {
    this.receiver = receiver;
  };
  AddSubMenuCommand.prototype.execute = function () {
    this.receiver.add();
  };
  var DelSubMenuCommand = function (receiver) {
    this.receiver = receiver;
  };
  DelSubMenuCommand.prototype.execute = function () {
    console.log('刪除子菜單');
  }
  var button1 = document.getElementById('button1'),
    button2 = document.getElementById('button2'),
    button3 = document.getElementById('button3');
  var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
  var addSubMenuCommand = new AddSubMenuCommand(SubMenu);
  var delSubMenuCommand = new DelSubMenuCommand(SubMenu);
  setCommand(button1, refreshMenuBarCommand);
  setCommand(button2, addSubMenuCommand);
  setCommand(button3, delSubMenuCommand);

2)撤消和重做
很多時候匕积,我們需要撤銷一系列的命令盈罐。比如在一個圍棋程序中,現(xiàn)在已經(jīng)下了10 步棋闪唆,我們需要一次性悔棋到第5 步盅粪。在這之前,我們可以把所有執(zhí)行過的下棋命令都儲存在一個歷史列表中悄蕾,然后倒序循環(huán)來依次執(zhí)行這些命令的undo 操作票顾,直到循環(huán)執(zhí)行到第5 個命令為止。然而帆调,在某些情況下無法順利地利用undo 操作讓對象回到execute 之前的狀態(tài)奠骄。比如在一個Canvas 畫圖的程序中,畫布上有一些點番刊,我們在這些點之間畫了N 條曲線把這些點相互連接起來含鳞,當(dāng)然這是用命令模式來實現(xiàn)的。但是我們卻很難為這里的命令對象定義一個擦除某條曲線的undo 操作芹务,因為在Canvas 畫圖中锄禽,擦除一條線相對不容易實現(xiàn)磁滚。這時候最好的辦法是先清除畫布,然后把剛才執(zhí)行過的命令全部重新執(zhí)行一遍铝侵,這一點同樣可以利用一個歷史列表堆棧辦到鸟雏。記錄命令日志,然后重復(fù)執(zhí)行它們松捉,這是逆轉(zhuǎn)不可逆命令的一個好辦法。

//html
<button id="replay">播放錄像</button>
//js
 var Ryu = {
    attack: function () {
      console.log("攻擊");
    },
    defense: function () {
      console.log("防御");
    },
    jump: function () {
      console.log("跳躍");
    },
    crouch: function () {
      console.log("蹲下");
    }
  }
  var commands = {
    "119": "jump", // W
    "115": "crouch", // S
    "97": "defense", // A
    "100": "attack" // D
  };
  var commandStack=[];
  var makeCommand=function(receive,state){
    return function(){
      return receive[state]();
    }
  }
  document.onkeypress=function(e){
    var keyCode=e.keyCode;
    var command=makeCommand(Ryu,commands[keyCode]);
    if(command){
      command();
      commandStack.push(command);
    }
  }
  document.getElementById("replay").onclick=function(){
    var command=null;
    for(var i=0;i<commandStack.length;i++){
      command=commandStack.shift();
      if(command){
        command();
        i--;
      }
    }
  } 

宏命令

宏命令是一組命令的集合,通過執(zhí)行宏命令的方式丰捷,可以一次執(zhí)行一批命令叠穆。

var closeDoorCommand = {
    execute: function () {
      console.log('關(guān)門');
    }
  };
  var openPcCommand = {
    execute: function () {
      console.log('開電腦');
    }
  };
  var openQQCommand = {
    execute: function () {
      console.log('登錄QQ');
    }
  };

var MacroCommand=function(){
  return {
    commandsList:[],
    add:function(command){
      this.commandsList.push(command);
    },
    execute:function(){
      for(var i=0,command;command=this.commandsList[i++];){
        command.execute();
      }
    }
  }
}

var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();

輸出結(jié)果:

關(guān)門
開電腦
登錄QQ

組合模式

組合模式將對象組合成樹形結(jié)構(gòu)医清,以表示“部分?整體”的層次結(jié)構(gòu)会烙。 除了用來表示樹形結(jié)構(gòu)之外柏腻,組合模式的另一個好處是通過對象的多態(tài)性表現(xiàn)败匹,使得用戶對單個對象和組合對象的使用具有一致性掀亩。
1)提供了一種遍歷樹形結(jié)構(gòu)的方案,通過調(diào)用組合對象的execute 方法,程序會遞歸調(diào)用組合對象下面的葉對象的execute 方法炼七,所以我們的萬能遙控器只需要一次操作缆巧,便能依次完成關(guān)門、打開電腦豌拙、登錄QQ 這幾件事情陕悬。組合模式可以非常方便地描述對象部分?整體層次結(jié)構(gòu)。
2)利用對象多態(tài)性統(tǒng)一對待組合對象和單個對象按傅。利用對象的多態(tài)性表現(xiàn)捉超,可以使客戶端忽略組合對象和單個對象的不同。在組合模式中唯绍,客戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對象狂秦,而不需要關(guān)心它究竟是組合對象還是單個對象。

一些值得注意的地方

1)組合模式不是父子關(guān)系
組合模式的樹型結(jié)構(gòu)容易讓人誤以為組合對象和葉對象是父子關(guān)系推捐,這是不正確的裂问。組合模式是一種HAS-A(聚合)的關(guān)系,而不是IS-A牛柒。組合對象包含一組葉對象堪簿,但Leaf并不是Composite 的子類。組合對象把請求委托給它所包含的所有葉對象皮壁,它們能夠合作的關(guān)鍵是擁有相同的接口椭更。為了方便描述,本章有時候把上下級對象稱為父子節(jié)點蛾魄,但大家要知道虑瀑,它們并非真正意義上的父子關(guān)系。
2)對葉對象操作的一致性
組合模式除了要求組合對象和葉對象擁有相同的接口之外滴须,還有一個必要條件舌狗,就是對一組葉對象的操作必須具有一致性。比如公司要給全體員工發(fā)放元旦的過節(jié)費1000 塊扔水,這個場景可以運用組合模式痛侍,但如果公
司給今天過生日的員工發(fā)送一封生日祝福的郵件,組合模式在這里就沒有用武之地了魔市,除非先把今天過生日的員工挑選出來主届。只有用一致的方式對待列表中的每個葉對象的時候,才適合使用組合模式待德。
3)雙向映射關(guān)系
發(fā)放過節(jié)費的通知步驟是從公司到各個部門君丁,再到各個小組,最后到每個員工的郵箱里将宪。這本身是一個組合模式的好例子绘闷,但要考慮的一種情況是橡庞,也許某些員工屬于多個組織架構(gòu)。比如某位架構(gòu)師既隸屬于開發(fā)組簸喂,又隸屬于架構(gòu)組毙死,對象之間的關(guān)系并不是嚴格意義上的層次結(jié)構(gòu)燎潮,在這種情況下喻鳄,是不適合使用組合模式的,該架構(gòu)師很可能會收到兩份過節(jié)費确封。這種復(fù)合情況下我們必須給父節(jié)點和子節(jié)點建立雙向映射關(guān)系除呵,一個簡單的方法是給小組和員工對象都增加集合來保存對方的引用。但是這種相互間的引用相當(dāng)復(fù)雜爪喘,而且對象之間產(chǎn)生了過多的耦合性颜曾,修改或者刪除一個對象都變得困難,此時我們可以引入中介者模式來管理這些對象秉剑。
4) 用職責(zé)鏈模式提高組合模式性能
在組合模式中泛豪,如果樹的結(jié)構(gòu)比較復(fù)雜,節(jié)點數(shù)量很多侦鹏,在遍歷樹的過程中诡曙,性能方面也許表現(xiàn)得不夠理想。有時候我們確實可以借助一些技巧略水,在實際操作中避免遍歷整棵樹价卤,有一種現(xiàn)成的方案是借助職責(zé)鏈模式。職責(zé)鏈模式一般需要我們手動去設(shè)置鏈條渊涝,但在組合模式中慎璧,父對象和子對象之間實際上形成了天然的職責(zé)鏈。讓請求順著鏈條從父對象往子對象傳遞跨释,或者是反過來從子對象往父對象傳遞胸私,直到遇到可以處理該請求的對象為止,這也是職責(zé)鏈模式的經(jīng)典運用場景之一鳖谈。

<button id="button">按我</button>
var openAcCommand = {
            execute: function () {
                console.log('打開空調(diào)');
            }
        };
        var openTvCommand = {
            execute: function () {
                console.log('打開電視');
            }
        };
        var openSoundCommand = {
            execute: function () {
                console.log('打開音響');
            }
        };
        var closeDoorCommand = {
            execute: function () {
                console.log('關(guān)門');
            }
        };
        var openPcCommand = {
            execute: function () {
                console.log('開電腦');
            }
        };
        var openQQCommand = {
            execute: function () {
                console.log('登錄QQ');
            }
        };
        var MacroCommand = function () {
            return {
                commandsList: [],
                add: function (command) {
                    this.commandsList.push(command);
                },
                execute: function () {
                    for (var i = 0, command; command = this.commandsList[i++];) {
                        command.execute();
                    }
                }
            }
        }
        var macroCommand1 = MacroCommand();
        macroCommand1.add(openTvCommand);
        macroCommand1.add(openSoundCommand);
        var macroCommand2 = MacroCommand();
        macroCommand2.add(closeDoorCommand);
        macroCommand2.add(openPcCommand);
        macroCommand2.add(openQQCommand);
        var macroCommand = MacroCommand();
        macroCommand.add(openAcCommand);
        macroCommand.add(macroCommand1);
        macroCommand.add(macroCommand2);
        var setCommand = (function (command) {
            document.getElementById('button').onclick = function () {
                command.execute();
            }
        })(macroCommand);

輸出結(jié)果:

打開空調(diào)
打開電視
打開音響
關(guān)門
開電腦
登錄QQ

組合模式的例子——掃描文件夾

//js
var Folder = function (name) {
            this.name = name;
            this.files = [];
        }
        Folder.prototype.add = function (file) {
            this.files.push(file);
        }
        Folder.prototype.scan = function () {
            console.log("開始掃描文件夾1:" + this.name);
            for (var i = 0, file; file = this.files[i++];) {
                file.scan();
            }
        }
        var File = function (name) {
            this.name = name;
        }
        File.prototype.add = function (file) {
            throw new Error('文件下面不能再添加文件');
        }
        File.prototype.scan = function () {
            console.log("開始掃描文件夾:" + this.name);

        }
        var folder = new Folder('學(xué)習(xí)資料');
        var folder1 = new Folder('JavaScript');
        var folder2 = new Folder('jQuery');
        var file1 = new File('JavaScript 設(shè)計模式與開發(fā)實踐');
        var file2 = new File('精通jQuery');
        var file3 = new File('重構(gòu)與模式')
        folder1.add(file1);
        folder2.add(file2);
        folder.add(folder1);
        folder.add(folder2);
        folder.add(file3);
        folder.scan();

輸出結(jié)果:

開始掃描文件夾1:學(xué)習(xí)資料
開始掃描文件夾1:JavaScript
開始掃描文件夾:JavaScript 設(shè)計模式與開發(fā)實踐
開始掃描文件夾1:jQuery
開始掃描文件夾:精通jQuery
開始掃描文件夾:重構(gòu)與模式

引用父對象

組合對象保存了它下面的子節(jié)點的引用盖文,這是組合模式的特點,此時樹結(jié)構(gòu)是從上至下的蚯姆。但有時候我們需要在子節(jié)點上保持對父節(jié)點的引用五续,比如在組合模式中使用職責(zé)鏈時,有可能需要讓請求從子節(jié)點往父節(jié)點上冒泡傳遞龄恋。還有當(dāng)我們刪除某個文件的時候疙驾,實際上是從這個文件所在的上層文件夾中刪除該文件的。
現(xiàn)在來改寫掃描文件夾的代碼郭毕,使得在掃描整個文件夾之前它碎,我們可以先移除某一個具體的文件。

var Folder = function (name) {
            this.name = name;
            this.parent=null;
            this.files = [];
        }
        Folder.prototype.add = function (file) {
            file.parent=this;
            this.files.push(file);
        }
        Folder.prototype.scan = function () {
            console.log("開始掃描文件夾1:" + this.name);
            for (var i = 0, file; file = this.files[i++];) {
                file.scan();
            }
        }
        Folder.prototype.remove=function(){
            if(!this.parent){
                return false;
            }else{
                for(var i=0,len=this.parent.files.length;i<len;i++){
                    var file=this.parent.files[i];
                    if(file==this){
                        this.parent.files.splice(i,1);
                    }
                }
            }
        }
        var File = function (name) {
            this.name = name;
        }
        File.prototype.add = function (file) {
            throw new Error('文件下面不能再添加文件');
        }
        File.prototype.scan = function () {
            console.log("開始掃描文件夾:" + this.name);

        }

        var folder = new Folder('學(xué)習(xí)資料');
        var folder1 = new Folder('JavaScript');
        var folder2 = new Folder('jQuery');
        var file1 = new File('JavaScript 設(shè)計模式與開發(fā)實踐');
        var file2 = new File('精通jQuery');
        var file3 = new File('重構(gòu)與模式')
        folder1.add(file1);
        folder2.add(file2);
        folder.add(folder1);
        folder.add(folder2);
        folder.add(file3);
        folder1.remove();
        folder.scan();

輸出結(jié)果:

開始掃描文件夾1:學(xué)習(xí)資料 
開始掃描文件夾1:jQuery
開始掃描文件夾:精通jQuery
開始掃描文件夾:重構(gòu)與模式

模板方法模式

模板方法模式是一種只需使用繼承就可以實現(xiàn)的非常簡單的模式。
模板方法模式由兩部分結(jié)構(gòu)組成扳肛,第一部分是抽象父類傻挂,第二部分是具體的實現(xiàn)子類。通常在抽象父類中封裝了子類的算法框架,包括實現(xiàn)一些公共方法以及封裝子類中所有方法的執(zhí)行順序。子類通過繼承這個抽象類介褥,也繼承了整個算法結(jié)構(gòu)栋荸,并且可以選擇重寫父類的方法。

var Beverage = function () { };
        Beverage.prototype.boilWater = function () {
            console.log('把水煮沸');
        };
        Beverage.prototype.brew = function () {
            throw new Error('子類必須重寫brew 方法');
        };
        Beverage.prototype.pourInCup = function () {
            throw new Error('子類必須重寫pourInCup 方法');
        };
        Beverage.prototype.addCondiments = function () {
            throw new Error('子類必須重寫addCondiments 方法');
        };
        Beverage.prototype.customerWantsCondiments = function () {
            return true; // 默認需要調(diào)料
        };
        Beverage.prototype.init = function () {
            this.boilWater();
            this.brew();
            this.pourInCup();
            if (this.customerWantsCondiments()) { // 如果掛鉤返回true,則需要調(diào)料
                this.addCondiments();
            }
        };
        var CoffeeWithHook = function () { };
        CoffeeWithHook.prototype = new Beverage();
        CoffeeWithHook.prototype.brew = function () {
            console.log('用沸水沖泡咖啡');
        };
        CoffeeWithHook.prototype.pourInCup = function () {
            console.log('把咖啡倒進杯子');
        };
        CoffeeWithHook.prototype.addCondiments = function () {
            console.log('加糖和牛奶');
        };
        CoffeeWithHook.prototype.customerWantsCondiments = function () {
            return window.confirm('請問需要調(diào)料嗎?');
        };
        var coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.init();

輸出結(jié)果:

把水煮沸
用沸水沖泡咖啡
把咖啡倒進杯子
加糖和牛奶

好萊塢原則:別調(diào)用我,我調(diào)用你

var Beverage = function (param) {
            var boilWater = function () {
                console.log('把水煮沸');
            };
            var brew = param.brew || function () {
                throw new Error('必須傳遞brew 方法');
            };
            var pourInCup = param.pourInCup || function () {
                throw new Error('必須傳遞pourInCup 方法');
            };
            var addCondiments = param.addCondiments || function () {
                throw new Error('必須傳遞addCondiments 方法');
            };
            var F = function () { };
            F.prototype.init = function () {
                boilWater();
                brew();
                pourInCup();
                addCondiments();
            };
            return F;
        };
        var Coffee = Beverage({
            brew: function () {
                console.log('用沸水沖泡咖啡');
            },
            pourInCup: function () {
                console.log('把咖啡倒進杯子');
            },
            addCondiments: function () {
                console.log('加糖和牛奶');
            }
        });
        var Tea = Beverage({
            brew: function () {
                console.log('用沸水浸泡茶葉');
            },
            pourInCup: function () {
                console.log('把茶倒進杯子');
            },
            addCondiments: function () {
                console.log('加檸檬');
            }
        });
        var coffee = new Coffee();
        coffee.init();
        var tea = new Tea();
        tea.init();

輸出結(jié)果:

把水煮沸
用沸水沖泡咖啡
把咖啡倒進杯子
加糖和牛奶
把水煮沸
用沸水浸泡茶葉
把茶倒進杯子
加檸檬

享元模式

享元(flyweight)模式是一種用于性能優(yōu)化的模式幢码,“fly”在這里是蒼蠅的意思,意為蠅量級尖飞。享元模式的核心是運用共享技術(shù)來有效支持大量細粒度的對象症副。
如果系統(tǒng)中因為創(chuàng)建了大量類似的對象而導(dǎo)致內(nèi)存占用過高,享元模式就非常有用了政基。在JavaScript 中贞铣,瀏覽器特別是移動端的瀏覽器分配的內(nèi)存并不算多,如何節(jié)省內(nèi)存就成了一件非常有意義的事情腋么。

1)內(nèi)部狀態(tài)存儲于對象內(nèi)部咕娄。
2) 內(nèi)部狀態(tài)可以被一些對象共享。
3) 內(nèi)部狀態(tài)獨立于具體的場景珊擂,通常不會改變圣勒。
4) 外部狀態(tài)取決于具體的場景,并根據(jù)場景而變化摧扇,外部狀態(tài)不能被共享圣贸。
享元模式帶來的好處很大程度上取決于如何使用以及何時使用,一般來說扛稽,以下情況發(fā)生時
便可以使用享元模式:

1) 一個程序中使用了大量的相似對象吁峻。
2) 由于使用了大量對象,造成很大的內(nèi)存開銷在张。
3) 對象的大多數(shù)狀態(tài)都可以變?yōu)橥獠繝顟B(tài)用含。
4) 剝離出對象的外部狀態(tài)之后,可以用相對較少的共享對象取代大量對象帮匾。

var Model = function (sex) {
            this.sex = sex;
        }
        Model.prototype.takePhoto = function () {
            console.log('sex=' + this.sex + 'underwear=' + this.underwear);
        }
        var maleModel = new Model('male'),
            femaleModel = new Model('female');
        for (var i = 1; i <= 50; i++) {
            maleModel.underwear = 'underwear' + i;
            maleModel.takePhoto();
        };

對象池實現(xiàn)

var objectPoolFactory = function (createObjFn) {
            var objectPool = [];
            return {
                create: function () {
                    var obj = objectPool.length === 0 ?
                        createObjFn.apply(this, arguments) : objectPool.shift();
                    return obj;
                },
                recover: function (obj) {
                    objectPool.push(obj);
                }
            }
        };
        var iframeFactory = objectPoolFactory(function () {
            var iframe = document.createElement('iframe');
            document.body.appendChild(iframe);
            iframe.onload = function () {
                iframe.onload = null; // 防止iframe 重復(fù)加載的bug
                iframeFactory.recover(iframe); // iframe 加載完成之后回收節(jié)點
            }
            return iframe;
        });
        var iframe1 = iframeFactory.create();
        iframe1.src = 'http:// baidu.com';
        var iframe2 = iframeFactory.create();
        iframe2.src = 'http:// QQ.com';
        setTimeout(function () {
            var iframe3 = iframeFactory.create();
            iframe3.src = 'http:// 163.com';
        }, 3000);

職責(zé)鏈模式

定義:使多個對象都有機會處理請求啄骇,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對象連成一條鏈瘟斜,并沿著這條鏈傳遞該請求缸夹,直到有一個對象處理它為止痪寻。

var order = function (orderType, pay, stock) {
        if (orderType === 1) { // 500 元定金購買模式
            if (pay === true) { // 已支付定金
                console.log('500 元定金預(yù)購, 得到100 優(yōu)惠券');
            } else { // 未支付定金,降級到普通購買模式
                if (stock > 0) { // 用于普通購買的手機還有庫存
                    console.log('普通購買, 無優(yōu)惠券');
                } else {
                    console.log('手機庫存不足');
                }
            }
        }
        else if (orderType === 2) { // 200 元定金購買模式
            if (pay === true) {
                console.log('200 元定金預(yù)購, 得到50 優(yōu)惠券');
            } else {
                if (stock > 0) {
                    console.log('普通購買, 無優(yōu)惠券');
                } else {
                    console.log('手機庫存不足');
                }
            }
        }
        else if (orderType === 3) {
            if (stock > 0) {
                console.log('普通購買, 無優(yōu)惠券');
            } else {
                console.log('手機庫存不足');
            }
        }
    };
    order(1, true, 500); // 輸出: 500 元定金預(yù)購, 得到100 優(yōu)惠券

但這遠遠算不上一段值得夸獎的代碼虽惭。order 函數(shù)不僅巨大到難以閱讀橡类,而且需要經(jīng)常進行修改。現(xiàn)在我們采用職責(zé)鏈模式重構(gòu)這段代碼芽唇。

// 500 元訂單
    var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log('500 元定金預(yù)購, 得到100 優(yōu)惠券');
        } else {
            order200(orderType, pay, stock); // 將請求傳遞給200 元訂單
        }
    };
    // 200 元訂單
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log('200 元定金預(yù)購, 得到50 優(yōu)惠券');
        } else {
            orderNormal(orderType, pay, stock); // 將請求傳遞給普通訂單
        }
    };
    // 普通購買訂單
    var orderNormal = function (orderType, pay, stock) {
        if (stock > 0) {
            console.log('普通購買, 無優(yōu)惠券');
        } else {
            console.log('手機庫存不足');
        }
    };
    // 測試結(jié)果:
    order500(1, true, 500); // 輸出:500 元定金預(yù)購, 得到100 優(yōu)惠券
    order500(1, false, 500); // 輸出:普通購買, 無優(yōu)惠券
    order500(2, true, 500); // 輸出:200 元定金預(yù)購, 得到500 優(yōu)惠券
    order500(3, false, 500); // 輸出:普通購買, 無優(yōu)惠券
    order500(3, false, 0); // 輸出:手機庫存不足

目前已經(jīng)有了不小的進步顾画,但我們不會滿足于此,雖然已經(jīng)把大函數(shù)拆分成了互不影響的3個小函數(shù)披摄,但可以看到亲雪,請求在鏈條傳遞中的順序非常僵硬勇凭,傳遞請求的代碼被耦合在了業(yè)務(wù)函數(shù)之中,這依然是違反開放?封閉原則的疚膊,如果有天我們要增加300 元預(yù)訂或者去掉200 元預(yù)訂,意
味著就必須改動這些業(yè)務(wù)函數(shù)內(nèi)部虾标。就像一根環(huán)環(huán)相扣打了死結(jié)的鏈條寓盗,如果要增加、拆除或者
移動一個節(jié)點璧函,就必須得先砸爛這根鏈條傀蚌。我們采用一種更靈活的方式,來改進上面的職責(zé)鏈模式蘸吓,目標(biāo)是讓鏈中的各個節(jié)點可以靈活拆分和重組善炫。

var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log('500 元定金預(yù)購,得到100 優(yōu)惠券');
        } else {
            return 'nextSuccessor'; // 我不知道下一個節(jié)點是誰库继,反正把請求往后面?zhèn)鬟f
        }
    };
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log('200 元定金預(yù)購箩艺,得到50 優(yōu)惠券');
        } else {
            return 'nextSuccessor'; // 我不知道下一個節(jié)點是誰,反正把請求往后面?zhèn)鬟f
        }
    };
    var orderNormal = function (orderType, pay, stock) {
        if (stock > 0) {
            console.log('普通購買宪萄,無優(yōu)惠券');
        } else {
            console.log('手機庫存不足');
        }
    };
    var Chain = function (fn) {
        this.fn = fn;
        this.successor = null;
    };
    Chain.prototype.setNextSuccessor = function (successor) {
        return this.successor = successor;
    };
    Chain.prototype.passRequest = function () {
        var ret = this.fn.apply(this, arguments);
        if (ret === 'nextSuccessor') {
            return this.successor && this.successor.passRequest.apply(this.successor, arguments);
        }
        return ret;
    };
    var chainOrder500 = new Chain(order500);
    var chainOrder200 = new Chain(order200);
    var chainOrderNormal = new Chain(orderNormal);
    chainOrder500.setNextSuccessor(chainOrder200);
    chainOrder200.setNextSuccessor(chainOrderNormal);
    chainOrder500.passRequest(1, true, 500); // 輸出:500 元定金預(yù)購艺谆,得到100 優(yōu)惠券
    chainOrder500.passRequest(2, true, 500); // 輸出:200 元定金預(yù)購,得到50 優(yōu)惠券
    chainOrder500.passRequest(3, true, 500); // 輸出:普通購買拜英,無優(yōu)惠券
    chainOrder500.passRequest(1, false, 0); // 輸出:手機庫存不足
var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log('500 元定金預(yù)購静汤,得到100 優(yōu)惠券');
        } else {
            return 'nextSuccessor'; // 我不知道下一個節(jié)點是誰,反正把請求往后面?zhèn)鬟f
        }
    };
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log('200 元定金預(yù)購居凶,得到50 優(yōu)惠券');
        } else {
            return 'nextSuccessor'; // 我不知道下一個節(jié)點是誰虫给,反正把請求往后面?zhèn)鬟f
        }
    };
    var orderNormal = function (orderType, pay, stock) {
        if (stock > 0) {
            console.log('普通購買,無優(yōu)惠券');
        } else {
            console.log('手機庫存不足');
        }
    };
    Function.prototype.after = function (fn) {
        var self = this;
        return function () {
            var ret = self.apply(this, arguments);
            if (ret === 'nextSuccessor') {
                return fn.apply(this, arguments);
            }
            return ret;
        }
    };
    var order = order500.after(order200).after(orderNormal);
    order(1, true, 500); // 輸出:500 元定金預(yù)購侠碧,得到100 優(yōu)惠券
    order(2, true, 500); // 輸出:200 元定金預(yù)購抹估,得到50 優(yōu)惠券
    order(1, false, 500); // 輸出:普通購買,無優(yōu)惠券

中介者模式

中介者模式的作用就是解除對象與對象之間的緊耦合關(guān)系舆床。增加一個中介者對象后棋蚌,所有的相關(guān)對象都通過中介者對象來通信嫁佳,而不是互相引用,所以當(dāng)一個對象發(fā)生改變時谷暮,只需要通知中介者對象即可蒿往。中介者使各對象之間耦合松散,而且可以獨立地改變它們之間的交互湿弦。中介者模式使網(wǎng)狀的多對多關(guān)系變成了相對簡單的一對多關(guān)系瓤漏。

function Player(name){
  this.name=name;
  this.enemy=null;
}
Player.prototype.win=function(){
  console.log(this.name+'won');
}
Player.prototype.lose=function(){
  console.log(this.name+'lose');
}
Player.prototype.die=function(){
  this.lose();
  this.enemy.win();
}
var player1 = new Player( '皮蛋' );
var player2 = new Player( '小乖' );
player1.enemy = player2;
player2.enemy = player1;
player1.die();// 輸出:皮蛋 lost、小乖 won
var players = [];
  function Player(name, teamColor) {
    this.name = name;
    this.enemies = [];
    this.state = 'live';
    this.partners = [];
    this.teamColor = teamColor;
  }
  Player.prototype.win = function () {
    console.log(this.name + 'won');
  }
  Player.prototype.lose = function () {
    console.log(this.name + 'lose');
  }
  Player.prototype.die = function () {
    var all_dead = true;
    this.state = 'dead';
    for (var i = 0, play; play = this.partners[i++];) {
      if (play.state == 'live') {
        all_dead = false;
      }
    }
    if (all_dead) {
      this.lose();
      for (var i = 0, play; play = this.partners[i++];) {
        play.lose();
      }
      for (var i = 0, enemy; enemy = this.enemies[i++];) {
        enemy.win();
      }
    }
  }
  var playerFactory = function (name, teamColor) {
    var newPlay = new Player(name, teamColor);
    for (var i = 0, play; play = players[i++];) {
      if (play.teamColor == teamColor) {
        newPlay.partners.push(play);
        play.partners.push(newPlay);
      } else {
        newPlay.enemies.push(play);
        play.enemies.push(newPlay);
      }
    }
    players.push(newPlay);
    return newPlay;
  }
  var player1 = playerFactory('皮蛋', 'red'),
    player2 = playerFactory('小乖', 'red'),
    player3 = playerFactory('寶寶', 'red'),
    player4 = playerFactory('小強', 'red');
  //藍隊:
  var player5 = playerFactory('黑妞', 'blue'),
    player6 = playerFactory('蔥頭', 'blue'),
    player7 = playerFactory('胖墩', 'blue'),
    player8 = playerFactory('海盜', 'blue');
  player1.die();
  player2.die();
  player4.die();
  player3.die();

執(zhí)行結(jié)果:


<html>

<body>
    選擇顏色:
    <select id="colorSelect">
        <option value="">請選擇</option>
        <option value="red">紅色</option>
        <option value="blue">藍色</option>
    </select>
    選擇內(nèi)存:
    <select id="memorySelect">
        <option value="">請選擇</option>
        <option value="32G">32G</option>
        <option value="16G">16G</option>
    </select>
    輸入購買數(shù)量:
    <input type="text" id="numberInput" />
    <br/> 您選擇了顏色:
    <div id="colorInfo"></div>
    <br/> 您選擇了內(nèi)存:
    <div id="memoryInfo"></div>
    <br/> 您輸入了數(shù)量:
    <div id="numberInfo"></div>
    <br/>
    <button id="nextBtn" disabled="true">請選擇手機顏色和購買數(shù)量</button>
</body>
<script>
    var goods = { // 手機庫存
        "red|32G": 3,
        "red|16G": 0,
        "blue|32G": 1,
        "blue|16G": 6
    };
    var mediator = (function () {
        var colorSelect = document.getElementById('colorSelect'),
            memorySelect = document.getElementById('memorySelect'),
            numberInput = document.getElementById('numberInput'),
            colorInfo = document.getElementById('colorInfo'),
            memoryInfo = document.getElementById('memoryInfo'),
            numberInfo = document.getElementById('numberInfo'),
            nextBtn = document.getElementById('nextBtn');
        return {
            changed: function (obj) {
                var color = colorSelect.value, // 顏色
                    memory = memorySelect.value,// 內(nèi)存
                    number = numberInput.value, // 數(shù)量
                    stock = goods[color + '|' + memory]; // 顏色和內(nèi)存對應(yīng)的手機庫存數(shù)量
                if (obj === colorSelect) { // 如果改變的是選擇顏色下拉框
                    colorInfo.innerHTML = color;
                } else if (obj === memorySelect) {
                    memoryInfo.innerHTML = memory;
                } else if (obj === numberInput) {
                    numberInfo.innerHTML = number;
                }
                if (!color) {
                    nextBtn.disabled = true;
                    nextBtn.innerHTML = '請選擇手機顏色';
                    return;
                }
                if (!memory) {
                    nextBtn.disabled = true;
                    nextBtn.innerHTML = '請選擇內(nèi)存大小';
                    return;
                }
                if (((number - 0) | 0) !== number - 0) { // 輸入購買數(shù)量是否為正整數(shù)
                    nextBtn.disabled = true;
                    nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
                    return;
                }
                if(stock<number){
                  nextBtn.disabled = true;
                    nextBtn.innerHTML = '庫存不足颊埃!';
                  return;
                }
                nextBtn.disabled = false;
                nextBtn.innerHTML = '放入購物車';
            }
        }
    })();
    // 事件函數(shù):
    colorSelect.onchange = function () {
        mediator.changed(this);
    };
    memorySelect.onchange = function () {
        mediator.changed(this);
    };
    numberInput.oninput = function () {
        mediator.changed(this);
    };
</script>

</html>

裝飾者模式

裝飾者模式將一個對象嵌入另一個對象之中蔬充,實際上相當(dāng)于這個對象被另一個對象包裝起來,形成一條包裝鏈班利。請求隨著這條鏈依次傳遞到所有的對象饥漫,每個對象都有處理這條請求的機會。

var plane = {
        fire: function () {
            console.log('發(fā)射普通子彈');
        }
    }
    var missileDecorator = function () {
        console.log('發(fā)射導(dǎo)彈');
    }
    var atomDecorator = function () {
        console.log('發(fā)射原子彈');
    }
    var fire1 = plane.fire;
    plane.fire = function () {
        fire1();
        missileDecorator();
    }
    var fire2 = plane.fire;
    plane.fire = function () {
        fire2();
        atomDecorator();
    }
    plane.fire();
// 分別輸出: 發(fā)射普通子彈罗标、發(fā)射導(dǎo)彈庸队、發(fā)射原子彈
var a = function () {
        alert(1);
    }
    var _a = a;
    a = function () {
        _a();
        alert(2);
    }
    a();
//彈出1,彈出2
 window.onload = function () {
        alert(1);
    }
    var _onload = window.onload || function () { };
    window.onload = function () {
        _onload();
        alert(2);
    }
//彈出1,彈出2
 <html>
        <button id="button"></button>
        <script>
            var _getElementById = document.getElementById;
            document.getElementById = function(){
                alert(1);
                return _getElementById.apply( document, arguments );
            }
            var button = document.getElementById( 'button' );
        </script>

    </html>

注釋:此時_getElementById 是一個全局函數(shù),當(dāng)調(diào)用一個全局函數(shù)時闯割,this 是指向window 的彻消,而document.getElementById 方法的內(nèi)部實現(xiàn)需要使用this 引用,this 在這個方法內(nèi)預(yù)期是指向document宙拉,而不是window


 Function.prototype.before = function (beforefn) {
        var __self = this; // 保存原函數(shù)的引用
        return function () { // 返回包含了原函數(shù)和新函數(shù)的"代理"函數(shù)
            beforefn.apply(this, arguments); // 執(zhí)行新函數(shù)宾尚,且保證this 不被劫持,新函數(shù)接受的參數(shù)
            // 也會被原封不動地傳入原函數(shù)谢澈,新函數(shù)在原函數(shù)之前執(zhí)行
            return __self.apply(this, arguments); // 執(zhí)行原函數(shù)并返回原函數(shù)的執(zhí)行結(jié)果煌贴,
            // 并且保證this 不被劫持
        }
    }
    Function.prototype.after = function (afterfn) {
        var __self = this;
        return function () {
            var ret = __self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret;
        }
    };
    window.onload = function () {
        alert(1);
    }
    window.onload = (window.onload || function () { }).after(function () {
        alert(2);
    }).after(function () {
        alert(3);
    }).after(function () {
        alert(4);
    });
<html>
<button tag="login" id="button">點擊打開登錄浮層</button>
<script>
    Function.prototype.after = function (afterfn) {
        var __self = this;
        return function () {
            var ret = __self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret;
        }
    };
    var showLogin = function () {
        console.log('打開登錄浮層');
    }
    var log = function () {
        console.log('上報標(biāo)簽為: ' + this.getAttribute('tag'));
    }
    showLogin = showLogin.after(log); // 打開登錄浮層之后上報數(shù)據(jù)
    document.getElementById('button').onclick = showLogin;
</script>

</html>
<html>
<body>
    用戶名:
    <input id="username" type="text" /> 密碼:
    <input id="password" type="password" />
    <input id="submitBtn" type="button" value="提交">
</body>
<script>
    var username = document.getElementById('username'),
        password = document.getElementById('password'),
        submitBtn = document.getElementById('submitBtn');
    var formSubmit = function () {
        if (username.value === '') {
            return alert('用戶名不能為空');
        }
        if (password.value === '') {
            return alert('密碼不能為空');
        }
        var param = {
            username: username.value,
            password: password.value
        }
        ajax('http:// xxx.com/login', param); // ajax 具體實現(xiàn)略
    }
    submitBtn.onclick = function () {
        formSubmit();
    }
</script>
</html>

注釋:formSubmit 函數(shù)在此處承擔(dān)了兩個職責(zé),除了提交ajax 請求之外澳化,還要驗證用戶輸入的合法性崔步。這種代碼一來會造成函數(shù)臃腫,職責(zé)混亂缎谷,二來談不上任何可復(fù)用性井濒。

<html>

<body>
    用戶名:
    <input id="username" type="text" /> 密碼:
    <input id="password" type="password" />
    <input id="submitBtn" type="button" value="提交">
</body>
<script>
    var username = document.getElementById('username'),
        password = document.getElementById('password'),
        submitBtn = document.getElementById('submitBtn');
    var validata = function () {
        if (username.value === '') {
            alert('用戶名不能為空');
            return false;
        }
        if (password.value === '') {
            alert('密碼不能為空');
            return false;
        }
    }
    var formSubmit = function () {
        if (validata() === false) { // 校驗未通過
            return;
        }
        var param = {
            username: username.value,
            password: password.value
        }
        ajax('http:// xxx.com/login', param);
    }
    submitBtn.onclick = function () {
        formSubmit();
    }
</script>

</html>

注釋:現(xiàn)在的代碼已經(jīng)有了一些改進,我們把校驗的邏輯都放到了validata 函數(shù)中列林,但formSubmit函數(shù)的內(nèi)部還要計算validata 函數(shù)的返回值瑞你,因為返回值的結(jié)果表明了是否通過校驗。
接下來進一步優(yōu)化這段代碼希痴,使validata 和formSubmit 完全分離開來者甲。首先要改寫Function.prototype.before, 如果beforefn 的執(zhí)行結(jié)果返回false砌创,表示不再執(zhí)行后面的原函數(shù)虏缸,代碼如下:

<html>
<body>
    用戶名:
    <input id="username" type="text" /> 密碼:
    <input id="password" type="password" />
    <input id="submitBtn" type="button" value="提交">
</body>
<script>
    var username = document.getElementById('username'),
        password = document.getElementById('password'),
        submitBtn = document.getElementById('submitBtn');
    Function.prototype.before = function (beforefn) {
        var __self = this;
        return function () {
            if (beforefn.apply(this, arguments) === false) {
                // beforefn 返回false 的情況直接return鲫懒,不再執(zhí)行后面的原函數(shù)
                return;
            }
            return __self.apply(this, arguments);
        }
    }
    var validata = function () {
        if (username.value === '') {
            alert('用戶名不能為空');
            return false;
        }
        if (password.value === '') {
            alert('密碼不能為空');
            return false;
        }
    }
    var formSubmit = function () {
        var param = {
            username: username.value,
            password: password.value
        }
        ajax('http:// xxx.com/login', param);
    }
    formSubmit = formSubmit.before(validata);
    submitBtn.onclick = function () {
        formSubmit();
    }
</script>
</html>

裝飾者模式和第6 章代理模式的結(jié)構(gòu)看起來非常相像,這兩種模式都描述了怎樣為對象提供一定程度上的間接引用刽辙,它們的實現(xiàn)部分都保留了對另外一個對象的引用窥岩,并且向那個對象發(fā)送請求。代理模式和裝飾者模式最重要的區(qū)別在于它們的意圖和設(shè)計目的宰缤。代理模式的目的是颂翼,當(dāng)直接訪問本體不方便或者不符合需要時,為這個本體提供一個替代者慨灭。本體定義了關(guān)鍵功能朦乏,而代理提供或拒絕對它的訪問,或者在訪問本體之前做一些額外的事情氧骤。裝飾者模式的作用就是為對象動態(tài)加入行為呻疹。換句話說,代理模式強調(diào)一種關(guān)系(Proxy 與它的實體之間的關(guān)系)语淘,這種關(guān)系可以靜態(tài)的表達诲宇,也就是說际歼,這種關(guān)系在一開始就可以被確定惶翻。而裝飾者模式用于一開始不能確定對象的全部功能時。代理模式通常只有一層代理?本體的引用鹅心,而裝飾者模式經(jīng)常會形成一條長長的裝飾鏈吕粗。
在虛擬代理實現(xiàn)圖片預(yù)加載的例子中,本體負責(zé)設(shè)置img 節(jié)點的src旭愧,代理則提供了預(yù)加載的功能颅筋,這看起來也是“加入行為”的一種方式,但這種加入行為的方式和裝飾者模式的偏重點是不一樣的输枯。裝飾者模式是實實在在的為對象增加新的職責(zé)和行為议泵,而代理做的事情還是跟本體一樣,最終都是設(shè)置src桃熄。但代理可以加入一些“聰明”的功能先口,比如在圖片真正加載好之前,

狀態(tài)模式

狀態(tài)模式是一種非同尋常的優(yōu)秀模式瞳收,它也許是解決某些需求場景的最好方法碉京。雖然狀態(tài)模式并不是一種簡單到一目了然的模式(它往往還會帶來代碼量的增加),但你一旦明白了狀態(tài)模式的精髓螟深,以后一定會感謝它帶給你的無與倫比的好處谐宙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市界弧,隨后出現(xiàn)的幾起案子凡蜻,更是在濱河造成了極大的恐慌搭综,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件划栓,死亡現(xiàn)場離奇詭異设凹,居然都是意外死亡茅姜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門奋姿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來素标,“玉大人,你說我怎么就攤上這事头遭。” “怎么了计维?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵袜香,是天一觀的道長。 經(jīng)常有香客問我鲫惶,道長蜈首,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任欠母,我火速辦了婚禮欢策,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赏淌。我一直安慰自己踩寇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布六水。 她就那樣靜靜地躺著俺孙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缩擂。 梳的紋絲不亂的頭發(fā)上鼠冕,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音胯盯,去河邊找鬼懈费。 笑死,一個胖子當(dāng)著我的面吹牛博脑,可吹牛的內(nèi)容都是我干的憎乙。 我是一名探鬼主播票罐,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泞边!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起阵谚,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤奠蹬,失蹤者是張志新(化名)和其女友劉穎囤躁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宵距,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡消玄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年携龟,在試婚紗的時候發(fā)現(xiàn)自己被綠了峡蟋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕊蝗。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖子漩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情紧显,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布重父,位于F島的核電站房午,受9級特大地震影響郭厌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扇售,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一承冰、第九天 我趴在偏房一處隱蔽的房頂上張望困乒。 院中可真熱鬧,春花似錦百宇、人聲如沸携御。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廓译。三九已至,卻和暖如春盹廷,著一層夾襖步出監(jiān)牢的瞬間管怠,已是汗流浹背渤弛。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晴氨,地道東北人瑞筐。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像闰非,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容