日前发布的dojo 1.7版本对其源码进行了很大的变动。在迈向2.0版本之际,dojo提供了许多新的功能,也对许多已有的功能进行了修改,具体来讲新版本更好地支持AMD规范、提供了新的事件处理系统(计划在2.0版本中替换dojo.connect API)和DOM查询模块、更新对象存储相关接口(ObjectStore)等。在本文中咱们将会介绍在dojo 1.7版本中新增的面向方面编程(AOP)功能以及其实现原理。编程
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,因此在此不对这个方法进行更细致的分析了。app
在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方法来声明模块及其依赖关系。有了类之后,咱们须要建立一个实例,以下:dom
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中能够添加任意的业务逻辑代码并调用目标方法,示例代码以下:ui
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中进行调用。this
经过对以上几种类型advice使用方式的介绍,咱们能够看到dojo的AOP功能在JavaScript中实现了AOP Alliance所倡导的advice类型。须要指出的是,每种类型的advice都可添加多个,dojo会按照添加的顺序依次执行。spa
了解了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模块的使用以及实现原理,尽管这种将静态语言编程风格移植到脚本语言中的作法可否被你们接受并普遍使用尚有待时间的检验,但这种尝试和实现方式仍是很值得借鉴的。