[TOC]
# 裝飾者模式
給對(duì)象動(dòng)態(tài)地增加職責(zé)的方式稱(chēng)為裝飾者模式。
裝飾者模式能夠在不改變對(duì)象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)象動(dòng)態(tài)地添加職責(zé)。跟繼承相比,裝飾者是一種更輕便靈活的做法,這是一種“即用即付”的方式。
## 模擬傳統(tǒng)面向?qū)ο笳Z(yǔ)言的裝飾者模式
```javascript
var Plan = function(){};
Plan.prototype.fire = function(){
console.log('普通子彈');
};
var MissileDecorator = function(plan){
this.plan = plan;
};
MissileDecorator.prototype.fire = function(){
this.plan.fire();
console.log('導(dǎo)彈');
};
var AtomDecorator = function(plan){
this.plan = plan;
};
AtomDecorator.prototype.fire = function(){
this.plan.fire();
console.log('原子彈');
};
var plan = new Plan();
plan = new MissileDecorator(plan);
plan = new AtomDecorator(plan);
plan.fire();
```
這種給對(duì)象動(dòng)態(tài)增加職責(zé)的方式,并沒(méi)有真正地改動(dòng)對(duì)象自身,而是將對(duì)象放入另一個(gè)對(duì)象之中,這些對(duì)象以一條鏈的方式進(jìn)行引用,形成一個(gè)聚合對(duì)象。這些對(duì)象都擁有相同的接口,當(dāng)請(qǐng)求到達(dá)鏈中的某個(gè)對(duì)象時(shí),這個(gè)對(duì)象會(huì)執(zhí)行自身的操作,隨后把請(qǐng)求轉(zhuǎn)發(fā)給鏈中的下一個(gè)對(duì)象。
## 裝飾者也是包裝器
裝飾者模式將一個(gè)對(duì)象嵌入另一個(gè)對(duì)象之中,實(shí)際上相當(dāng)于這個(gè)對(duì)象被另一個(gè)對(duì)象包裝起來(lái),形成一條包裝鏈。
## 回到JavaScript的裝飾者
```javascript
var plan = {
fire: function(){
console.log('普通子彈');
}
};
var missilDecorator = function(){
console.log('導(dǎo)彈');
};
var atomDecorator = function(){
console.log('原子彈');
};
var fire1 = plane.fire;
plan.fire = function(){
fire1();
missilDecorator();
};
var fire2 = plan.fire;
plan.fire = function(){
fire2();
atomDecorator();
};
plan.fire();
```
## 裝飾函數(shù)
通過(guò)保存原引用的方式就可以改寫(xiě)某個(gè)函數(shù)
```javascript
var a = function(){
alert(1);
};
var _a = a;
a = function(){
_a();
alert(2);
};
a();
```
但是這種方式存在兩個(gè)問(wèn)題:
- 必須維護(hù)一個(gè)中間變量,如果裝飾函數(shù)的裝飾鏈較長(zhǎng),或者需要裝飾的函數(shù)變多,這些中間變量的數(shù)量也會(huì)越來(lái)越多。
- 還會(huì)遇到`this`被劫持的問(wèn)題。
## 用AOP裝飾函數(shù)
給出`Function.prototype.before`和`Function.prototype.after`方法
```javascript
Function.prototype.before = function(beforeFn){
var _self = this; // 保存原函數(shù)的引用
return function(){ // 返回包含了原函數(shù)和新函數(shù)的“代理”函數(shù)
beforefn.apply(this, arguments); // 執(zhí)行新函數(shù),且保證this不被劫持,新函數(shù)接受的參數(shù)也會(huì)原封不動(dòng)地傳入原函數(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;
}
};
```
`Function.prototype.before`接受一個(gè)函數(shù)當(dāng)作參數(shù),這個(gè)函數(shù)即為新添加的函數(shù),它裝載了新添加的功能。
接下來(lái)把當(dāng)前的`this`保存起來(lái),這個(gè)`this`指向原函數(shù),然后返回一個(gè)“代理”函數(shù),這個(gè)“代理”函數(shù)只是結(jié)構(gòu)上像代理而已,并不承擔(dān)代理的職責(zé)。它的工作是把請(qǐng)求分別轉(zhuǎn)發(fā)給新添加的函數(shù)和原函數(shù),且負(fù)責(zé)保證它們的執(zhí)行順序,讓新添加的函數(shù)在原函數(shù)之前執(zhí)行,這樣就實(shí)現(xiàn)了動(dòng)態(tài)裝飾的效果。
## AOP的應(yīng)用實(shí)例
用`AOP`裝飾函數(shù)的技巧在實(shí)際開(kāi)發(fā)中非常有用。不論是業(yè)務(wù)代碼的編寫(xiě),還是在框架層面,都可以把行為依照職責(zé)分成粒度更細(xì)的函數(shù),隨后通過(guò)裝飾把它們合并到一起,有助于編寫(xiě)一個(gè)松耦合和高復(fù)用性的系統(tǒng)。
### 數(shù)據(jù)統(tǒng)計(jì)上報(bào)
```javascript
Function.prototype.after = function(afterFn){
var _self = this;
return function(){
var ret = _self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
};
var showLogiun = function(){
console.log('打開(kāi)登錄浮層');
};
var log = function(){
console.log('上報(bào)數(shù)據(jù)');
};
showLogin = showLogin.after(log);
document.getElementById('login').onclick = showLogin;
```
### 用AOP動(dòng)態(tài)改變函數(shù)的參數(shù)
```javascript
Function.prototype.before = function(beforeFn){
var _self = this;
return function(){
beforefn.apply(this, arguments);
return _self.apply(this, arguments);
}
};
var func = function(param){
console.log(param); // 輸出:{a: 'a', b:'b'}
};
func = func.before(function(param){
param.b = 'b';
});
func({a: 'a'});
```
### 插件式的表單驗(yàn)證
```javascript
Function.prototype.before = function(beforeFn){
var _self = this;
return function(){
if(beforefn.apply(this, arguments) === false){
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('....');
};
formSubmit = formSubimt.before(validata);
submitBtn.onclick = function(){
formSubmit();
};
```
函數(shù)通過(guò)`Function.prototype.before`或者`Function.prototype.after`被裝飾之后,返回的實(shí)際上是一個(gè)新的函數(shù),如果在原函數(shù)上保存了一些屬性,那么這些屬性會(huì)丟失。
這種裝飾方式也疊加了函數(shù)的作用域,如果裝飾的鏈條過(guò)長(zhǎng),性能上也會(huì)受到一些影響。
