在程序開發(fā)時(shí)喊括,我們不希望某個(gè)類或者函數(shù)的方法非常復(fù)雜,一次就包含很多職責(zé);那么我們可以采用裝飾者模式卧蜓,它可以在不改變?cè)鷮?duì)象的基礎(chǔ)上,動(dòng)態(tài)地給某個(gè)對(duì)象添加一些額外的方法或者屬性把敞,使其滿足更復(fù)雜的用戶需求弥奸。
實(shí)現(xiàn)
一個(gè)例子,我們希望在原有的onload基礎(chǔ)上添加一個(gè)方法奋早,而我們不想改變?cè)械膐nload方法(或者方法太復(fù)雜盛霎,總之我們保持器神秘性),那么我們可以這么做:
window.onload = funciton(){// 不知道的邏輯 ***}
function my(){alert(2);} // 我自己的邏輯
var _onload = window.onload || function(){};
window.onload = funciton(){
_onload();
my();
}
上面這樣的做法有可能會(huì)產(chǎn)生一個(gè)問(wèn)題耽装,那就是作用域的不延續(xù)(也就是this被劫持的問(wèn)題)摩渺,比如:
var _getElementById = document.getElementById;
document.getElementById = function(id){
alert(1);
return _getElementById( id ); // 調(diào)用的時(shí)候會(huì)拋出異常,因?yàn)間etElementById需要document作用域剂邮。
}
為了解決這類問(wèn)題摇幻,同時(shí)提出一個(gè)比較適用的裝飾者解決方案,我們借組AOP進(jìn)行實(shí)現(xiàn):
// 實(shí)現(xiàn)1 本文都采用此方法
Function.prototype.before = function( fn ){
var _self = this;
return function(){
fn.apply(this, arguments);
_self.apply(this, arguments);
}
}
Function.prototype.after = function( fn ){
var _self = this;
return function(){
_self.apply(this, arguments);
fn.apply(this, arguments);
}
}
// 實(shí)現(xiàn)2
Function.prototype._$aop = function(_before,_after){
var f = function(){},
_after = _after||f,
_before = _before||f,
_handler = this;
return function(){
var _event = {args:[].slice.call(arguments,0)};
_before(_event);
if (!_event.stopped){
_event.value = _handler
.apply(this,_event.args);
_after(_event);
}
return _event.value;
};
};
那么可以把上面的方法進(jìn)行如下裝飾:
document.getElementById = document.getElementById.before(function(){
alert(1);
})
實(shí)用
幾個(gè)用到AOP的地方:
-
數(shù)據(jù)統(tǒng)計(jì): 如果我們要統(tǒng)計(jì)事件挥萌,假設(shè)點(diǎn)擊出現(xiàn)登錄框時(shí)要發(fā)送統(tǒng)計(jì)請(qǐng)求绰姻,可以采用如下所示的方法:
var showLogin = function(){ // open the dialog } var log = function(){ // 上報(bào) } showLogin = showLogin.after(log);
-
改變函數(shù)參數(shù): 當(dāng)我們網(wǎng)站受到攻擊時(shí),需要在ajax請(qǐng)求中加上一個(gè)token參數(shù)引瀑,如下:
// 原來(lái)的ajax var ajax = funciton( type, url, param ){// ajax邏輯} // 修改后的 var ajax = function( type, url, param ){ param = param || {}; param.token = getToken(); ajax(type, url, param); // ajax邏輯 } // 如果我們又不想改變?cè)瓉?lái)的ajax庫(kù)(有可能以后新的項(xiàng)目需要用到狂芋,互聯(lián)網(wǎng)總是很多的新項(xiàng)目..) ajax = ajax.before(function(type, url, param){ param.token = getToken(); });
-
校驗(yàn)需求: 提交表單時(shí),校驗(yàn)不通過(guò)憨栽,直接返回帜矾。
Function.prototype.before = function( fn ){ var _self = this; return function(){ if (fn.apply(this, arguments) === false){ return; } _self.apply(this, arguments); } } function formSumbit(){ // ajax提交 } function validate(){ if( user.name === ''){ alert("名字為空"); return false; } ... } formSumbit = formSumbit.before(validate);
裝飾者模式和代理模式的區(qū)別
代理模式的目的是翼虫,當(dāng)直接訪問(wèn)本體不方便或者不符合需求時(shí),為這個(gè)本體提供一個(gè)代替者屡萤。本體定義了關(guān)鍵功能珍剑,而代理提供或拒絕對(duì)它的訪問(wèn),或者在訪問(wèn)本體前做一些額外的事情死陆。 換句話說(shuō)招拙,代理模式強(qiáng)調(diào)一種關(guān)系(Proxy與它的實(shí)體之間的關(guān)系),這種關(guān)系一開始就可以被確定措译。以圖片加載為例别凤,為圖片設(shè)置src時(shí)最終目的,而在之前設(shè)置一個(gè)loading圖片是一個(gè)聰明的做法领虹。
裝飾者模式是為對(duì)象動(dòng)態(tài)的加入行為规哪,用于不能確定本體對(duì)象全部功能的情況下,因此有可能形成一條長(zhǎng)長(zhǎng)的裝飾鏈塌衰。