裝飾者,英文名叫decorator. 所謂的"裝飾",從字面能夠很容易的理解出,就是給 土肥圓,化個妝,華麗的轉身爲白富美,但本體仍是土肥圓。 ajax
說人話.
咳咳~編程
在js裏面一切都是對象,並且函數就是一等對象。 在普通的Object中,咱們能夠很容易的添加屬性或者其餘方法,固然函數也不例外。 可是,這樣作的後果就是,咱們會不斷的改變本體,就像把鳳姐送去作整形手術同樣。 固然,結果有好有壞,也許鳳姐就是下一個angelababy,也許... 因此,爲了咱們代碼的純潔度(就算你是醜小鴨), 咱們能夠不動刀子的狀況下,讓你變得又白又美。安全
這個是裝飾的初級階段,就是抹點粉而已。 在js中,咱們叫作引用裝飾。app
talk is cheap, show u code框架
//咱們給jimmy函數額外添加其餘的功能 var jimmy = function(){ console.log("jimmy"); } var _jimmy = jimmy; jimmy = function(){ _jimmy(); console.log("I love HuaHua"); } jimmy();
這個的應用場景主要就是在多人協做和框架設計裏面。好比,李冰岩已經使用了onload函數,可是,小舟又想使用onload函數。 這樣會形成一個問題,若是小舟直接改動的話,他須要看的是李冰岩寫的蜜汁代碼,並且還要防止不會引發錯誤。這無疑是很困難的,因此在這裏,可使用引用裝飾,來給onload在添加一層。函數
//這是小李的蜜汁代碼 var xiaoLi = function(){ console.log("蜜汁代碼"); } window.onload = xiaoLi; //小李已經綁定好onload函數了 //接下來小舟須要改動onload代碼 var fn = window.onload; var xiaoZhou = function(){ fn(); conosle.log("整潔代碼"); } window.onload = function(){ //根據onload的特性,直接覆蓋掉小李的code xiaoZhou(); }
因此引用裝飾的用處仍是蠻大的。
大你妹啊~~
啊。。。。
(另外一Me) 咱們來分析一下,上面那個引用模式有什麼弊端(好處已經都知道了).
首先,咱們使用引用模式的時候,一定會添加一個多餘的引用對象,好比上文的"fn".
並且隨着你程序鏈的增長,中間對象必定會和你節點同等數量的。固然,起名我就不說了,關鍵是,一大堆無用的代碼放在那裏,感受很不爽的。 因此,這裏引入AOP的裝飾模式.工具
親切,熟悉,完美。 咱們見過AOP應該不止一次了,在職責鏈模式使用過,在迭代器模式使用過等等。使用這麼屢次,好像尚未對AOP作一些基本解釋呢?因此,這裏給你們咻咻.
AOP中文名叫面向切面編程。 先說一下這個名詞,「面向」這詞應該不用解釋,關鍵"切面"是什麼鬼。 若是你們作過sangwich,應該就知道,首先咱們買來一塊麪包,須要將麪包切開,而後在切面上面加上一些flavoring,好比蔬菜,火腿,培根之類的。 恩,對比js程序來講,一個程序鏈就至關於你買回來的麪包,flavoring就是你想加的功能函數,如何將函數正確的放置在程序鏈中合適的位置,這就是AOP作的事情。
首先,再次將兩個動態函數咻咻:性能
Function.prototype.after = function(fn){ var _this = this; return function(){ var res = _this.apply(this,arguments); fn.apply(this,arguments); return res; } } Function.prototype.before = function(fn){ var _this = this; return function(){ fn.apply(this,arguments); return _this.apply(this,arguments); } }
這兩個函數的組合構成了js中AOP模式的精華.而AOP最經常使用的就是講與業務邏輯無關的功能動態織入到主程序中。測試
talk is cheap , show u codethis
舉個栗子吧: 使用AOP計算程序運行事件
//純手寫計算函數運行事件 function factorial(n) { //最基本的階乘計算 if (n === 1) return 1; return n * factorial(n - 1); } function calTime(n){ var start = new Date().getMilliseconds(); factorial(n); console.log(new Date().getMilliseconds() - start+"ms"); } calTime(1000);
能夠得出耗費的時間,可是,若是還有其餘的函數須要測試,那麼這麼作的意義並無很大的價值。咱們使用AOP進行重構。
function factorial(n) { //最基本的階乘計算 if (n === 1) return 1; return n * factorial(n - 1); } var calTime = (function(){ var start; return function(){ if(!start){ //給開始時間賦值 start = new Date().getMilliseconds(); }else{ console.log(new Date().getMilliseconds()-start+"ms"); start = undefined; } } })(); var calcu = factorial.before(calTime).after(calTime)(200);
這樣很好的將計時功能從業務邏輯中提取出來,並且看着真的頗有angelababy的味道誒.
使用AOP的時候須要注意一點就是,before&after執行完後,返回的結果都是第一個函數的內容。
var result = function(){ return 1; }.before(function(){ return 2; }).after(function(){ return 3; }); console.log(result()); //1
咱們大體的瞭解了AOP的用法和理論,能夠針對於開頭所說的例子進行重構.
window.onload = function(){ console.log("小李的蜜汁代碼"); } var fn = window.onload; fn.before(function(){ console.log("整潔代碼"); }); window.onload = fn;
看起來,比上面那個栗子清晰不少,並且使用before和after也十分利於代碼的閱讀。
上面那個例子,只能算是AOP裝飾模式的一個不起眼的角落。 AOP引用的場景在js中,或者說在任何一門語言中都是大放光彩的。 在js中,"細粒度"對象是程序中複用性最高的對象,能把對象用細粒度的形式表示,那麼AOP無疑是最佳的選擇。
在寫業務邏輯的時候,咱們最大的問題就是判斷邏輯,使用大量的if語句,而這均可以通過思考巧妙化解。好比,我在寫購買課程的時候就會遇到一些邏輯。 只有當課程數目符合要求的時候,購買的效果纔能有效.
按正常的業務邏輯編寫
var buy = function(){ if(confirm()){ //驗證購買信息是否合法 http.buyCourse('xxx'); //發起請求 } } var confirm = function(){ console.log("驗證購買數量"); } document.querySelector(".buy").addEventListener("click",function(){ buy(); },false);
使用AOP裝飾模式重構後
var buy = function(){ http.buyCourse("xxx"); //給後臺發起請求,驗證 } var confirm = function(){ console.log("驗證購買數量"); } var buy = buy.before(confirm); document.querySelector(".buy").addEventListener("click",function(){ buy(); },false);
美美代碼的 滿滿即視感!!!
不夠,老闆,再來份糖炒栗子~
好嘞~
這裏咱們只是獲取函數的結果,那咱們想直接干預傳遞的參數,能夠嗎?
固然能夠。
咱們研究一下,before的內部構造(after是同樣的)
Function.prototype.before = function(fn){ var _this = this; return function(){ fn.apply(this,arguments); return _this.apply(this,arguments); } }
這裏,因爲arguments是引用類型,若是fn改變了arguments,則會反映到_this.apply的arguments也會發生改變。 而這個應用場景就是,給ajax的地址添加上你須要的參數。
在實際項目中,一開始的接口,就是一個普普統統的地址,發請求,而後獲取參數。
http.ajax(url,type).then(...)
想這樣的使用,可是某天,你的leader提升了要求等級,將地址後面都加上一個token參數,或者說一個口令的md5或sha1的計算值,我想,這尼瑪工做量應該不小。
固然,咱們能夠直接將url進行傳遞。
var http = { ajax1(url){ url += param.getToken(); sendAjax(url); }, ajax2(url){ ... } ... }
並且,萬一哪天你的leader說,哎,這樣作安全性仍是不過高,要不加兩個token混淆一下。
啊~啊~啊~啊~(混淆你妹啊,過不過年啦)
若是你繼續這麼寫,我相信,年終獎是有的,可是,春運火車票你估計是摸不着了。
因此可使用AOP進行動態織入。要知道,參數,我AOP也是能夠動的。
function dealUrl(url){ url+=param.getToken(); } http.ajax = http.ajax.before(dealUrl); http.ajax("www.example.com"); //此時的Url = www.example.com?token=23jkfd3kjfdksjfkjds
並且,這個處理url函數,我也是能夠扔到任意一個js文件裏面複用的耶.
棒!!!
我AOP能夠動你要的參數,並且,我還能夠把個人結果給你是咻咻,若是我不讓你執行,你永遠也不會執行,哈哈哈哈~~~~
對不起,,上面那段是我意淫AOP說的。。。 其實AOP能夠算是萬能的配置工具,好比表單驗證吧。 咱們常常會把表單驗證和表單結果發送耦合在一塊兒。
像這樣
var sendRes = function(){ if(user.userName === ""){ alert("用戶名不能爲空~"); return; }else if(user.password === ""){ alert("密碼不能爲空~"); return; } http.sendUser("xxx"); //驗證成功發送用戶信息 }
一個函數裏面同時含有了兩個職責,一個驗證一個發送用戶信息。 因此咱們如今的主要目的就是解耦。
回想一下,之前表單驗證咱們使用策略模式,解耦了驗證,這裏咱們再次使用。
var sendRes = function(){ var detect = new Detect(); //策略者模式 detect.add(user.userName,["notEmpty"]); detect.add(user.password,["notEmpty"]); var notPass = detect.getResult(); if(notPass){ //若是沒經過 console.log(notPass); return; } http.sendUser("xxx"); //驗證成功發送用戶信息 }
可使用上面那個驗證,可是結果是,驗證和策略模式仍是在一塊兒。咱們再使用AOP進行解耦。首先咱們得重構一下before函數
Function.prototype.before = function(fn){ var _this = this; return function(){ var res = fn.apply(this,arguments); //值爲Boolean,表示是否繼續向下傳遞 if(res==="next"){ //若是返回不成立,則繼續向下傳遞 return _this.apply(this,arguments); } return res; } }
看到這裏,有些同窗應該明白是怎麼一回事了。沒錯,就是根據before裏面驗證的結果判斷是否執行下個發送請求的功能函數。
固然,若是不想污染原型,你也能夠自定義一個函數。
var before = function(beforeFn,fn){ return function(){ var res = beforeFn.apply(this,arguments); if(res==="next"){ return fn.apply(this,arguments); } } }
這樣寫也是能夠的。
咱們先按原型方式寫,這樣直觀一點
var sendRes = function(){ http.sendUser("xxx"); //驗證成功發送用戶信息 } sendRes = sendRes.before(function(){ var detect = new Detect(); //策略者模式 detect.add(user.userName,["notEmpty"]); detect.add(user.password,["notEmpty"]); var notPass = detect.getResult(); if(notPass){ //若是沒經過 console.log(notPass); } return "next"; });
能夠看出,驗證那部分已經徹底和發送用戶信息的功能函數徹底給解耦了。 這樣不只提升了函數的重用性,並且也讓你的代碼朝着「細粒度」方向大步前進.
其實,裝飾者模式和職責鏈模式的形式是徹底同樣的,因此,他們的弊端也是相似的。鏈造的過長,對於性能來講就是一次rape.因此,仍是那句話,不要爲了模式而模式,沒有萬能的模式。