iT邦幫忙

DAY 12
3

Javascript面面觀系列 第 12

Javascript面面觀:核心篇《模式-Reflection, Proxy and AOP》

Spring Framework有兩個主要的功能,就是IoC容器與AOP。有接觸過AOP的人,就會知道AOP有許多好處,主要是有許多程式的需求其實跟程式的邏輯沒有直接關係,但是又需要在適時插入到邏輯中,最常見的就是在程式中插入紀錄log的功能。像這類的需求可以把它稱作Aspect或是cross-cutting concern,AOP(Aspect Oriented Programming)主要的目的,就是集中處理這一類的需求,讓這一類的需求與邏輯可以拆開,但是又可以在適當的時機介入到程式邏輯中。

AOP可以通過Proxy Pattern來實作,也就是說,透過一個Proxy來執行程式,因為是透過Proxy,所以有機會在做執行動作的前後做介入。Proxy有兩種,一種是靜態Proxy,它必須為每個要透過Proxy執行的類別設計一個相同介面的Proxy類別,然後透過這個Proxy來執行這個類別,這樣在實作上非常麻煩。自從Java有了Reflection能力後,可以用他來偵測類別的介面然後動態地產生Proxy來執行類別,這樣可以節省很大的功夫。

Javascript具備有基本的Reflection功能,所以也有可能用這個功能來實現動態Proxy,然後用來實作AOP。
結構
Javascript可以利用for...in語法,來列舉出一個物件所擁有的properties。像這樣:

 function a() {
     this.p1 = "p1";
     this.showP1 = function() {
         alert(this.p1);
     };
 }
 var b = new a();
 var str = '';
 for (var i in b) {
     str += '[' + i + "]\n";
 }
 alert(str);

同時,Javascript可以用[]來存取物件的Properties。像這樣:

 function a() {
     this.p1 = "p1";
     this.showP1 = function() {
         alert(this.p1);
     };
 }
 var b = new a();
 alert(b['p1']);
 b['showP1']();

透過這兩種語法,Javascript就可以有Reflection的能力。不過這有個限制,一些內建的物件,他的屬性可能會是[[DontEnum]],也就是不可列舉的,而自訂的物件在下一版的ECMA-262中,也可以這樣設定他的屬性。如此一來可能會讓一些Properties不會被列舉出來。所以要能完整支援Reflection的物件,就不能額外設定這個property的屬性。

底下就實作一個動態Proxy物件,並讓它可以透過constructor來傳入被代理的物件,並設定要在被代理物件的函數執行前、執行後、以及包裹在所執行函數外的函數。除了使用Reflection,為了方便把函數包裹起來,我從google的base.js裡面偷來了一個函數叫做bind,它可以在函數執行前先把參數指派給它,這樣到了要實際執行時,我只要用函數的參考後面加上()就可以正確執行。下一版的ECMA-262,也會把這個函數加入到Function.prototype中,所以Javascript的函數都可以先把他的參數用這個方法先bind上去。

 (function(){
 	var bind = function(fn, selfObj, var_args) {
 		var boundArgs = fn.boundArgs_;
 		if (arguments.length > 2) {
 			var args = Array.prototype.slice.call(arguments, 2);
 			if (boundArgs) {
 				args.unshift.apply(args, boundArgs);
 			}
 			boundArgs = args;
 		}
 		selfObj = fn.boundSelf_ || selfObj;
 		fn = fn.boundFn_ || fn;
 		var newfn;
 		var context = selfObj || goog.global;
 		if (boundArgs) {
 			newfn = function() {
 				var args = Array.prototype.slice.call(arguments);
 				args.unshift.apply(args, boundArgs);
 				return fn.apply(context, args);
 			};
 		} else {
 			newfn = function() {
 				return fn.apply(context, arguments);
 			};
 		}
 		newfn.boundArgs_ = boundArgs;
 		newfn.boundSelf_ = selfObj;
 		newfn.boundFn_ = fn;
 		return newfn;
 	};
 	AOP = function(o, c) {
 		var methods = [];
 		var before = function(){};
 		var after = function(){};
 		var arround = function(f){f();};
 		if (c.before && typeof c.before === 'function') before = c.before;
 		if (c.after && typeof c.after === 'function') after = c.after;
 		if (c.arround && typeof c.arround === 'function') arround = c.arround;
 		if (typeof o == 'function') {
 			var o = new o();
 		}
 		for (var i in o) {
 			if (typeof o[i] == 'function') {
 				methods.push(i);
 			}
 		}
 		i = 0;
 		for (; i<methods.length; i++) {
 			this[methods[i]] = function(methodName) {
 				return function() {
 					before(methodName);
 					arround( bind( o[methodName], o, Array.prototype.slice.call(arguments) ) , methodName);
 					after(methodName);
 				};
 			}(methods[i]);
 		}
 	};
 })();

接下來可以實驗一下:

 function a() {
 	this.show=function(msg) {
 		alert('a.show: ' + msg);
 	}
 }
 var b = new a();
 b.show('hey');

執行這個範例中的b.show('hey'),就會顯示'a.show: hey'。接下來我們用上面實作的AOP類別來包裹b,產生物件變數c,並且加入在他的函數執行前後,及包裹了函數並在執行前後顯示訊息的程式:

 var c = new AOP(b, {
 	before: function(m){alert('before method: '+m);},
 	after: function(m){alert('after method: '+m);},
 	arround: function(f, m){
 		alert('before within arround method: '+m);
 		f();
 		alert('after within arround method: '+m);
 	}
 });
 c.show('hey');

這樣就會依序顯示訊息:'before method: show'、'before within arround method: show'、'a.show: hey'、'after within arround method: show'、'after method: show'。

上例可以透過http://jsbin.com/icuxi來試用。

應用
範例裡面只是一個非常粗糙的實作,功能也有限。要看別人怎麼做、怎麼用,會更有價值。

我上網找了一下,YUI3這個Javascript框架裡面,有在Custom Event架構中實作AOP,也許可以拿來參考。另外,如果我自己要拿來用的話,也許可以在做單元測試的時候,用它來產生mock object,只是還沒仔細研究是否可行。


上一篇
Javascript面面觀:核心篇《模式-decorator》
下一篇
Javascript面面觀:核心篇《ECMA-262 Edition 5》(上)
系列文
Javascript面面觀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言