在编写js的时候,我们有时会遇到针对某种场景做处理,比如在方法开始的时候校验参数,执行方法前检查权限,或是删除前给出确认提示等等。这些校验方法、权限检测、确认提示,规则可能都是相同的,在每个方法前去调用,显得麻烦,而且不利于统一管理,于是我们想到了面向切面编程(AOP)。
1. 简单AOP实现
简单的AOP实现,就是在原函数执行的前后,增加运行before和after两个增强方法,用这个新函数替换原函数,为此,我编写了一个类似于构造器的函数(后文就称它为构造器),代码如下:
// originFun为原函数,before和after为增强方法 ???function constructor(originFun, before, after){ ???????function _class(){ ???????????before.apply(this, arguments); ???????????originFun.apply(this, arguments); ???????????after.apply(this, arguments); ???????} ???????return _class; ???}
使用时,用构造器生成的新函数替换原函数即可,测试代码如下:
// 加法运算,作为测试的原函数 ???function calcAdd(a,b){ ???????console.log(a + "+" + b + "=" + (a + b)); ???????return a+b; ???} ???// AOP增强 ???calcAdd = constructor(calcAdd, function(){console.log("我在原方法前执行")}, function(){console.log("我在原方法后执行")}); ???// 调用方法进行测试 ???calcAdd(2, 3);
打印在控制台的测试结果:
我在原方法前执行2+3=5我在原方法后执行
2. AOP工厂
在某些场景下,使用的增强方法是相同的,每次将增强方法作为参数传递有点麻烦,于是我做了一个工厂方法,把增强方法作为参数,这样就可以生成不同的构造器,在不同场景调用不同的构造器即可:
// AOP工厂var aopFactory = function(before, after){ ???????// 构造方法,在原方法前后增加执行方法 ???function constructor(originFun){ ???????function _class(){ ???????????var result; ???????????proxy.before(arguments); ???????????result = originFun.apply(this, arguments); ???????????proxy.after(arguments); ???????????return result; ???????} ???????return _class; ???} ???????var proxy = { ???????// 添加被代理方法,参数a为被代理方法,参数b为目标对象 ???????add : function(a, b){ ???????????var funName; ???????????// 判断参数a类型,可以为方法或方法名 ???????????if(typeof a == "function"){ ???????????????funName = a.name; ???????????}else if(typeof a == "string"){ ???????????????funName = a; ???????????}else{ ???????????????return; ???????????} ???????????// 不传对象时默认为window对象 ???????????b = b || window; ???????????if(typeof b == "object" && b[funName]){ ???????????????// 替换原方法 ???????????????b[funName] = constructor(b[funName]); ???????????} ???????}, ???????// 默认before为空方法 ???????before : function(){}, ???????// 默认after为空方法 ???????after : function(){} ???} ???????// 注入特定的前后处理方法 ???if(typeof before == "function"){ ???????proxy.before = before; ???} ???if(typeof after == "function"){ ???????proxy.after = after; ???} ???????return proxy;}
测试代码如下:
var printProxy, checkProxy; ???????// 打印参数 ???function printArguments(){ ???????var i, length; ???????for(i=0, length=arguments.length; i<length; i++){ ???????????console.info("param" + (i + 1) + " = " + arguments[i]); ???????} ???} ???// 验证参数是否为数字 ???function checkNumber(){ ???????var i, length; ???????for(i=0, length=arguments.length; i<length; i++){ ???????????if(typeof arguments[i] != "number") ???????????????console.error(arguments[i] + "不是数字"); ???????} ???} ???????// 将printArguments方法作为前置通知,生成打印参数的构造器 ???printProxy = aopFactory(printArguments); ???// 将checkNumber方法作为前置通知,生成验证参数是否为数字的构造器 ???checkProxy = aopFactory(checkNumber); ???????// 加法 ???function calcAdd(a,b){ ???????console.log(a + "+" + b + "=" + (a + b)); ???????return a+b; ???} ???// 减法 ???function calcMinus(a,b){ ???????console.log(a + "-" + b + "=" + (a - b)); ???????return a-b; ???} ???// 乘法 ???function calcMultiply(a,b){ ???????console.log(a + "*" + b + "=" + (a * b)); ???????return a*b; ???} ???// 除法 ???function calcDivide(a,b){ ???????console.log(a + "/" + b + "=" + (a / b)); ???????return a/b; ???} ???????// 为加减法生成验证参数是否为数字的代理方法 ???checkProxy.add(calcAdd); ???checkProxy.add(calcMinus); ???// 为乘除法生成打印参数的代理方法 ???printProxy.add(calcMultiply); ???printProxy.add(calcDivide); ???????// 测试 ???calcAdd("4", 5); ???calcMinus(6, "a"); ???calcMultiply(4, 5); ???calcDivide(6, 3);
测试结果如下:
4不是数字 4+5=45a不是数字6-a=NaNparam1 = 4param2 = 54*5=20param1 = 6param2 = 36/3=2
3. 进一步优化AOP工厂
在before方法中,验证到参数不正确,我们并不想让方法继续执行下去。我们可以让before方法返回一个布尔值,作为停止执行的标志。在原方法执行前检查before方法的返回值,判断是否继续往下执行。
另外,为每个方法生成代理都要调用一次add,这还不够简单,于是想到了正则表达式,通过循环,把所以满足正则表达式的方法都进行增强。
根据以上两点得到新AOP工厂方法:
// 优化后的AOP工厂var aopFactory = function(before, after){ ???????// 构造方法,在原方法前后增加执行方法 ???function constructor(originFun){ ???????function _class(){ ???????????var result; ???????????result = proxy.before.apply(this,arguments); ???????????// 如果before方法返回false,则直接return不再往下执行 ???????????if(typeof result == "boolean" && !result){return;} ???????????result = originFun.apply(this, arguments); ???????????proxy.after.apply(this,arguments); ???????????return result; ???????} ???????return _class; ???} ???????var proxy = { ???????// 添加被代理方法,参数a为被代理方法,参数b为目标对象 ???????add : function(a, b){ ???????????var funName, index; ???????????// 不传对象时默认为window对象 ???????????b = b || window; ???????????if(typeof b != "object") ???????????????return; ???????????// 判断参数a类型,如果为正则表达式 ???????????if(typeof a == "object" && a.test){ ???????????????// 替换所以满足正则表达式的方法 ???????????????for(index in b){ ???????????????????if(a.test(index)){ ???????????????????????b[index] = constructor(b[index]); ???????????????????} ???????????????} ???????????????return; ???????????} ???????????// 判断参数a类型,取出方法名 ???????????if(typeof a == "function"){ ???????????????funName = a.name; ???????????}else if(typeof a == "string"){ ???????????????funName = a; ???????????}else{ ???????????????return; ???????????} ???????????// 如果方法存在,替换原方法 ???????????if(b[funName]){ ???????????????b[funName] = constructor(b[funName]); ???????????} ???????}, ???????// 默认before为空方法 ???????before : function(){}, ???????// 默认after为空方法 ???????after : function(){} ???} ???????// 注入特定的前后处理方法 ???if(typeof before == "function"){ ???????proxy.before = before; ???} ???if(typeof after == "function"){ ???????proxy.after = after; ???} ???????return proxy;}
测试代码:
var checkProxy, myFunc; ???????// 验证参数是否为数字,是数字就打印,否则给出错误提示 ???function checkNumber(){ ???????var i, length, flag = true; ???????for(i=0, length=arguments.length; i<length; i++){ ???????????if(typeof arguments[i] != "number"){ ???????????????console.error(arguments[i] + "不是数字"); ???????????????flag = false; ???????????}else{ ???????????????console.info("param" + (i + 1) + " = " + arguments[i]); ???????????} ???????} ???????return flag; ???} ???????// 将checkNumber方法作为前置通知,生成验证参数是否为数字的构造器 ???checkProxy = aopFactory(checkNumber); ???????myFunc = { ???????// 加法 ???????calcAdd : function(a,b){ ???????????console.log(a + "+" + b + "=" + (a + b)); ???????????return a+b; ???????}, ???????// 减法 ???????calcMinus : function (a,b){ ???????????console.log(a + "-" + b + "=" + (a - b)); ???????????return a-b; ???????}, ???????// 计算幂 ???????power : function(a,b){ ???????????console.log(a + "^" + b + "=" + Math.pow(a, b)); ???????????return Math.pow(a, b); ???????} ???} ???????// 对myFunc对象中所有"calc"开头的方法进行增强 ???checkProxy.add(/^(calc).*/, myFunc); ???????// 测试 ???console.log("calcAdd--------------"); ???myFunc.calcAdd(4, 5); ???console.log("calcMinus--------------"); ???myFunc.calcMinus(6, "a"); ???console.log("power--------------"); ???myFunc.power(4, 3);
测试结果:
calcAdd--------------param1 = 4param2 = 54+5=9calcMinus--------------param1 = 6a不是数字power--------------4^3=64
测试对myFunc中所有"calc"开头的方法进行增强,从结果可以看到,加减法运算前都打印了参数,而且减法运算由于参数不正确,并没有执行。幂运算不是我们希望增强的方法,它的参数没有被打印出来。
对于增强方法中有回调函数的情况,我想到的是把原函数及其参数作为回调函数的参数进行传递,没有发现什么更好方法,就不细说了。
最后,文章中有什么问题,或是大家有什么好的想法,记得告诉我哦!
JS中AOP的实现和运用
原文地址:http://www.cnblogs.com/zengyuanjun/p/7429968.html