声明:本文为笔者原创,但首发于InfoQ中文站,详见文末声明
日前发布的dojo 1.7版本对其源码进行了很大的变更。在迈向2.0版本之际,dojo提供了许多新的功能,也对许多已有的功能进行了修改,具体来说新版本更好地支持AMD规范、提供了新的事件处理系统(计划在2.0版本中替换dojo.connect API)和DOM查询模块、更新对象存储相关接口(ObjectStore)等。在本文中我们将会介绍在dojo 1.7版本中新增的面向方面编程(AOP)功能以及其实现原理。
1.
简介
AOP即面向方面编程,是面向对象编程思想的延续。利用该思想剥离一些通用的业务,可以有效降低业务逻辑间的耦合度,提高程序的可重用性。随着Java领域中Spring框架的流行,其倡导的AOP理念被更多的人所熟识(要注意的是Spring并不是该思想的首创者,但说Spring框架的流行让更多人了解和学习了AOP思想并不为过)。因为Java为静态语言,所以在实现AOP功能时较为复杂,一般采取两种方式即动态代理和字节码生成技术(如CGLib)来实现该功能。在JavaScript领域,因为以前模块化需求并不是很强烈,所以AOP的理念并没有被广泛引入进来。但是随着RIA技术的发展,越来越多的业务逻辑在前台完成,JavaScript代码的组织和可维护性越发重要,正是在这样的背景下,出现了很多JavaScript模块化管理的类库,而dojo也在这方面积极探索,新版本的dojo已经更好地支持AMD规范,并提供了面向方面编程的支持。
在面向方面编程功能推出之前,dojo可以通过使用connect方法来实现类似的功能。connect方法主要可以实现两类功能即为dom对象绑定事件和为已有的方法添加后置方法。已经有不少文章分析dojo的connect方法的使用和原理,再加上dojo计划在将来版本中移除该API,所以在此不对这个方法进行更细致的分析了。
2. dojo的aspect模块使用简介
在dojo 1.7的版本中新增了aspect模块,该模块主要用来实现AOP的功能。借助于此项功能可以为某个对象的方法在运行时添加before、after或around类型的增强(advice,即要执行的切面方法)。为了介绍此功能,我们先用dojo的类机制声明一个简单的类:
define("com.levinzhang.Person",["dojo/_base/declare"],function(declare){
declare("com.levinzhang.Person", null,{
name:null,
age:null,
constructor: function(name,age){
this.name = name;
this.age = age;
},
getName:function(){
return this.name;
},
getAge:function(){
return this.age;
},
sayMyself:function(){
alert("Person's name is "+this.getName()+"!");
}
});
})
这里声明类的方法,与我们在介绍类机制时略有不同,因为dojo从1.6版本开始支持AMD规范,通过define方法来声明模块及其依赖关系。有了类以后,我们需要创建一个实例,如下:
dojo.require("com.levinzhang.Person");
var person = new com.levinzhang.Person("levin",30);
现在我们要借助dojo的aspect模块为这个类的实例添加AOP功能。假设我们需要在sayMyself方法的调用前后分别添加对另一个方法的调用(即所谓的增强advice),示例代码如下:
var aspect = dojo.require("dojo.aspect");//引入aspect模块
//声明在person 的sayMyself方法调用前要调用的方法
var signal = aspect.before(person,"sayMyself",function(){
alert("调用了before");
});
//声明在person 的sayMyself方法调用后要调用的方法
aspect.after(person,"sayMyself",function(){
alert("调用了after");
});
//此时调用sayMyself方法将会先后打印出:
//“调用了before”、“Person's name is levin!”、“调用了after”
//即按照before、目标方法、after的顺序执行
person.sayMyself();
在以上的代码片段中,我们使用了aspect的before和after方法实现了在目标方法前后添加advice。在调用before和after方法后将会返回一个signal对象,这个对象记录了目标advice并提供了移除方法,如要移除上文添加的before
advice,只需执行以下代码:
signal.remove();//移除前面添加的beforeadvice
//此时调用sayMyself方法将会先后打印出:
// “Person's name is levin!”、“调用了after”
//即通过aspect.before添加的方法已经被移除
person.sayMyself();
除了before和after类型的advice,dojo还支持around类型的advice,在这种情况下,需要返回一个function,在这个function中可以添加任意的业务逻辑代码并调用目标方法,示例代码如下:
var signal = aspect.around(person, "sayMyself", function(original){
return function(){
alert("before the original method");
original.apply(person,arguments);//调用目标方法,即原始的sayMyself方法
alert("after the original method");
}
});
//此时调用sayMyself方法将会先后打印出:
//“before the original method”、“Person's name is levin!”、“after the original method”
person.sayMyself();
从上面的示例代码我们可以看到,around类型的advice会有更多对业务逻辑的控制权,原始的目标方法会以参数的形式传递进来,以便在advice中进行调用。
通过对以上几种类型advice使用方式的介绍,我们可以看到dojo的AOP功能在JavaScript中实现了AOP Alliance所倡导的advice类型。需要指出的是,每种类型的advice均可添加多个,dojo会按照添加的顺序依次执行。
3.
实现原理
了解了dojo AOP功能的基本语法后,让我们分析一下其实现原理。dojo aspect模块的实现在dojo/aspect.js文件中,整个文件的代码数在100行左右,因此其实现是相当简洁高效的。
通过var aspect
= dojo.require("dojo.aspect");方法引入该模块时,会得到一个简单的JavaScript对象,我们调用aspect.before、aspect.around、aspect.after时,均会调用该文件中定义的aspect方法所返回的function。
define([], function(){
……
return {
before: aspect("before"),
around: aspect("around"),
after: aspect("after")
};
});
现在我们看一下aspect方法的实现:
function aspect(type){
//对于不同类型的advice均返回此方法,只不过type参数会有所不同
return function(target, methodName, advice, receiveArguments){
var existing = target[methodName], dispatcher;
if(!existing || existing.target != target){
//经过AOP处理的方法均会被一个新的方法所替换,也就是这里的dispatcher
dispatcher = target[methodName] = function(){
// before advice
var args = arguments;
//得到第一个before类型的advice
var before = dispatcher.before;
while(before){
//调用before类型的advice
args = before.advice.apply(this, args) || args;
//找到下一个before类型的advice
before = before.next;
}
//调用around类型的advice
if(dispatcher.around){
调用dispatcher.around的advice方法
var results = dispatcher.around.advice(this, args);
}
//得到第一个after类型的advice
var after = dispatcher.after;
while(after){
//调用after类型的advice
results = after.receiveArguments ? after.advice.apply(this, args) || results :
after.advice.call(this, results);
//找到下一个after类型的advice
after = after.next;
}
return results;
};
if(existing){
//设置最初的around类型的advice,即调用目标方法
dispatcher.around = {advice: function(target, args){
return existing.apply(target, args);
}};
}
dispatcher.target = target;
}
//对于不同类型的advice,通用advise方法来修改dispatcher,即对象的同名方法
var results = advise((dispatcher || existing), type, advice, receiveArguments);
advice = null;
return results;
};
}
我们可以看到,在第一次调用aspect方法时,原有的目标方法会被替换成dispatcher方法,而在这个方法中会按照内部的数据结构,依次调用各种类型的advice和最初的目标方法。而构建和调整这个内部数据结构是通过advise方法来实现的:
function advise(dispatcher, type, advice, receiveArguments){
var previous = dispatcher[type];//得到指定类型的前一个advice
var around = type == "around";
var signal;
if(around){
//对around类型的advice,只需调用advice方法,并将上一个advice(有可能即为//目标方法)作为参数传入即可
var advised = advice(function(){
return previous.advice(this, arguments);
});
//构建返回的对象,即aspect.around方法的返回值
signal = {
//移除方法
remove: function(){
signal.cancelled = true;
},
advice: function(target, args){
//即为真正执行的around方法
return signal.cancelled ?
previous.advice(target, args) : //取消,跳至下一个
advised.apply(target, args); // 调用前面的advised方法
}
};
}else{
// 对于after或before类型的advice,构建移除方法
signal = {
remove: function(){
var previous = signal.previous;
var next = signal.next;
if(!next && !previous){
delete dispatcher[type];
}else{
if(previous){
previous.next = next;
}else{
dispatcher[type] = next;
}
if(next){
next.previous = previous;
}
}
},
advice: advice,
receiveArguments: receiveArguments
};
}
if(previous && !around){
if(type == "after"){
//将新增的advice加到列表的尾部
var next = previous;
while(next){
//移到链表尾部
previous = next;
next = next.next;
}
previous.next = signal;
signal.previous = previous;
}else if(type == "before"){
//将新增的advice添加到起始位置
dispatcher[type] = signal;
signal.next = previous;
previous.previous = signal;
}
}else{
// around类型的advice或第一个advice
dispatcher[type] = signal;
}
return signal;
}
以上,我们分析了dojo的aspect模块的使用以及实现原理,尽管这种将静态语言编程风格移植到脚本语言中的做法能否被大家接受并广泛使用尚有待时间的检验,但这种尝试和实现方式还是很值得借鉴的。
参考资料:
http://livedocs.dojotoolkit.org/
http://dojotoolkit.org/
声明:
本文已经首发于InfoQ中文站,版权所有,原文为《dojo1.7功能介绍:面向方面编程(AOP)功能与原理》,如需转载,请务必附带本声明,谢谢。
InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon 、线下技术交流活动QClub、免费迷你书下载如《架构师》等。
分享到:
相关推荐
Dojo 1.7 中文版本注释功能说明,以Dojo使用案例,很经典,做为系统了解Dojo功能及用法很有用!
Dojo1.7 Api chm 英文版
Dojo 1.7 版本注释 非常全面 收集了很就在这里和大家共享。共同学习。
最新dojo包,内含最新的实例若干个。是入门学习的好帮手!
<<Dojo的高级运用:Widget的制作>> 和 使用Dojo和JSON构建Ajax应用>> 中涉及到的源代码 博文链接:https://tailsherry.iteye.com/blog/102907
dojo 源码1.7汇总 包括dijit 和 dojo 打包汇总 非常好的
dojo dojo实例 dojo例子 dojo资料 dojo项目 dojo实战 dojo模块 dojo编程
其中 Dojo 是核心功能包 , Dijit 中存放的是 Dojo 所有的Widget 组件,而 DojoX 则是一些扩展或试验功能,DojoX 中的试验功能在成熟之后有可能在后续版本中移入到 Dojo 或 Dijit 命名空间中。列举了常用的一些包及其...
struts2的插件dojo-ajax编程:所需dojo.js,完整
Dojo是一个非常强大的、面向对象的、开源的JavaScript工具箱,它为开发富客户端Ajax应用提供了一套完整的小部件和一些特效操作。曾经有人这样说:“对于一个Web开发者而言,如果没有Dojo,他将是一个“残废”的...
压缩包里包含的资源: 1.dojo最新工具包:dojo-release-1.8.3.zip 2.dojoAPI chm文档:dojo1.8.chm
本文将会介绍dojo类机制幕后的知识,其中会涉及到dojo类机制的实现原理并对一些关键方法进行源码分析,当然在此之前希望您能够对JavaScript和dojo的使用有些基本的了解。
深刻剖析Dojo工作原理,Dojo之父执笔的权威之作,国内权威社区DOJO中国组织翻译。 Doio是一个功能强大的面向对象开源JavaScript工具包,它为开发新一代Web程序提供了一套完整的小部件和一些特效,得到了IBM、Sun、...
a、利用dojo提供的组件,你可以提升你的web应用程序可用性、交互能力以及功能上的提高; b、你也可以 更容易的建立degradeable user interfaces ??,dojo提供小巧的 widgets ,animate transitions; c、利用它的...
Include <SCRIPT TYPE="text/javascript" SRC="dojo/dojo.js"></SCRIPT> and you're on your way. Browse to dojo/tests/runTests.html or dijit/themes/themeTester.html to see Dojo in action
模块与包) 1 二、 Dojo学习笔记(2. djConfig解说) 4 三、 Dojo学习笔记(3. Dojo的基础对象和方法) 6 四、 Dojo学习笔记(4. dojo.string & dojo.lang) 9 五、 Dojo学习笔记(5. dojo.lang.array & dojo.lang.func & ...
文章用几个简单的实例,让初学者了解dojo的相关知识,和如何使用dojo的相关知识.
dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档 dojo文档