高階函數(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)模式的精髓螟深,以后一定會感謝它帶給你的無與倫比的好處谐宙。