以前學習java的Spring架構時,這是個很強大的東西,用於實現調用者和被調用者之間的解耦。雖然在JS中也提供了call與apply動態改變調用者,但在複雜的UI組件中,這是遠遠不夠了。前段時間也在無憂中看到一個類似的需求,說要“如何繼承自己”,辭不達意,亂七八糟,但回貼都是精華,讓我見識到AOP在JS的運用,逐研究了下這個東西。
以下是我最初的實現,它取用的方式是在某個執行個體方法的前面或後面織入通知函數。
// pointcut: 織入點對象 ,target:被織入的對象 ,method:被織入的方法名字 ,advice: 通知函數 function Person(){ this.say = function(){ alert("高談闊論!"); } this.cry = function(){ alert("鬼哭神嚎!"); } this.round = function(){ alert("天地無用!"); } } Aspects = function(){}; Aspects.prototype={ before:function(target,method,advice){ (advice)(); for(var i in target){ if(i == method){ target[i](); }; }; }, after:function(target,method,advice){ for(var i in target){ if(i == method){ target[i](); }; }; (advice)(); }, around:function(target,method,advice){ (advice)(); for(var i in target){ if(i == method){ target[i](); }; }; (advice)(); } } window.onload = function(){ var t = new Person; var a = new Aspects; a.before(t,"say",function(){alert("一馬當先")}) a.after(t,"cry",function(){alert("事後孔明")}) a.around(t, "round", function(){alert("十面埋伏")}) alert("==========================="); t.say(); }
<br /><script type="text/javascript"><br /> function Person(){<br /> this.say = function(){<br /> alert("高談闊論!");<br /> }<br /> this.cry = function(){<br /> alert("鬼哭神嚎!");<br /> }<br /> this.round = function(){<br /> alert("天地無用!");<br /> }<br /> }<br /> Aspects = function(){};<br /> Aspects.prototype={<br /> before:function(target,method,advice){<br /> (advice)();<br /> for(var i in target){<br /> if(i == method){<br /> target[i]();<br /> };<br /> };<br /> },<br /> after:function(target,method,advice){</p><p> for(var i in target){<br /> if(i == method){<br /> target[i]();<br /> };<br /> };<br /> (advice)();<br /> },<br /> around:function(target,method,advice){<br /> (advice)();<br /> for(var i in target){<br /> if(i == method){<br /> target[i]();<br /> };<br /> };<br /> (advice)();<br /> }<br /> }<br /> window.onload = function(){<br /> var t = new Person;<br /> var a = new Aspects;<br /> a.before(t,"say",function(){alert("一馬當先")})<br /> a.after(t,"cry",function(){alert("事後孔明")})<br /> a.around(t, "round", function(){alert("十面埋伏")})<br /> alert("===========================");<br /> t.say();<br /> }<br /></script><br />
運行代碼
非常糟糕!它馬上就執行了,而不等到我們調用t.say()時執行!改變一下思路,把通知函數織入執行個體方法中,然後在這個被織入的方法中執行原來的邏輯與新織入的邏輯!
function Person(){ this.say = function(name){ alert("我的名字叫做"+name+"……"); } } Aspects = function(){}; Aspects.prototype={ before:function(target,method,advice){ var original = target[method]; target[method] = function(){ (advice)(); original(); } return target }, after:function(target,method,advice){ var original = target[method]; target[method] = function(){ original(); (advice)(); } return target }, around:function(target,method,advice){ var original = target[method]; target[method] = function(){ (advice)(); original(); (advice)(); } return target } } window.onload = function(){ var t = new Person; var a = new Aspects; t = a.before(t,"say",function(){alert("請你介紹一下自己!")}); alert("===========================") t.say("司徒正美"); }
<br /> <script type="text/javascript"><br /> function Person(){<br /> this.say = function(name){<br /> alert("我的名字叫做"+name+"……");<br /> }<br /> }<br /> Aspects = function(){};<br /> Aspects.prototype={<br /> before:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original();<br /> }<br /> return target<br /> },<br /> after:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> original();<br /> (advice)();<br /> }<br /> return target<br /> },<br /> around:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original();<br /> (advice)();<br /> }<br /> return target<br /> }<br /> }<br /> window.onload = function(){<br /> var t = new Person;<br /> var a = new Aspects;<br /> t = a.before(t,"say",function(){alert("請你介紹一下自己!")});<br /> alert("===========================")<br /> t.say("司徒正美");<br /> }<br /> </script><br />
運行代碼
發現不能傳入參數,我們可以用apply來綁定參數。之所以選擇apply,是因為它比call更靈活。
/******************略**********************/ before:function(target,method,advice){ var original = target[method]; target[method] = function(){ (advice)(); original.apply(target, arguments); } return target },/******************略**********************/
<br /> <script type="text/javascript"><br /> function Person(){<br /> this.say = function(name,lang){<br /> alert("我的名字叫做"+name+",專註於"+lang+"……");<br /> }<br /> }<br /> Aspects = function(){};<br /> Aspects.prototype={<br /> before:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original.apply(target, arguments);<br /> }<br /> return target<br /> },<br /> after:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> original.apply(target, arguments);<br /> (advice)();<br /> }<br /> return target<br /> },<br /> around:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original.apply(target, arguments);<br /> (advice)();<br /> }<br /> return target<br /> }<br /> }<br /> window.onload = function(){<br /> var t = new Person;<br /> var a = new Aspects;<br /> t = a.before(t,"say",function(){alert("請你介紹一下自己!")});<br /> alert("===========================")<br /> t.say("司徒正美","javascript");<br /> }<br /> </script><br />
運行代碼
最後放出一個現實點的例子吧,看看你能聯想到什嗎?!
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <meta http-equiv="X-UA-Compatible" content="IE=Edge"><br /> <script type="text/javascript"><br /> function voice(){<br /> alert("救命啊!");<br /> }<br /> Aspects = function(){};<br /> Aspects.prototype={<br /> before:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original.apply(target, arguments);<br /> }<br /> return target<br /> },<br /> after:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> original.apply(target, arguments);<br /> (advice)();<br /> }<br /> return target<br /> },<br /> around:function(target,method,advice){<br /> var original = target[method];<br /> target[method] = function(){<br /> (advice)();<br /> original.apply(target, arguments);<br /> (advice)();<br /> }<br /> return target<br /> }<br /> }<br /> window.onload = function(){<br /> var bn = document.getElementById("bn");<br /> var a = new Aspects;<br /> a.after(bn,"onclick",function(){alert("HELP!HELP!")});<br /> }<br /> </script><br /> <title>非法修改button的onclick事件</title><br /> </head><br /> <body><br /> <input onclick="voice()" type="button" id="bn" value="動我就叫人來"><br /> </body><br /></html><br />
運行代碼
想到了嗎?聯想一下我們部落格園,可能以這個作例子有點不妥……它為了安全在我們點擊提交表單時,都會對我們輸入的內容進行正則替換,把一些危險的代碼過濾掉,或換成>、<之類不能啟動並執行編碼。如果我們用AOP hack一下這個提交按鈕,在部落格園替換完後我們再替換回去,就可以做出一些不為人齒的事來,當然不希望大家這樣做……我們應該用到正當的途徑上,如Ext就大量利用AOP來增強組件的功能了。又譬如,我們編寫了一個複雜的Grid控制項,但無法事先預計哪些方法需要觸發事件。這種情況下,你可以直接攔截控制項的某些方法來類比事件處理。另,在許可權驗證、內容傳輸、錯誤處理、調試、記錄跟蹤等方面,AOP都有一番作用,我們應該好好掌握這一設計模式,畢竟它被稱之為OOP的延伸與補充。