• JS中AOP的实现和运用


    在编写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);
    View Code

    测试结果如下:

    4不是数字 
    4+5=45
    a不是数字
    6-a=NaN
    param1 = 4
    param2 = 5
    4*5=20
    param1 = 6
    param2 = 3
    6/3=2
    View Result

       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);
    View Code

    测试结果:

    calcAdd--------------
    param1 = 4
    param2 = 5
    4+5=9
    calcMinus--------------
    param1 = 6
    a不是数字
    power--------------
    4^3=64
    View Result

    测试对myFunc中所有"calc"开头的方法进行增强,从结果可以看到,加减法运算前都打印了参数,而且减法运算由于参数不正确,并没有执行。幂运算不是我们希望增强的方法,它的参数没有被打印出来。

    对于增强方法中有回调函数的情况,我想到的是把原函数及其参数作为回调函数的参数进行传递,没有发现什么更好方法,就不细说了。

    最后,文章中有什么问题,或是大家有什么好的想法,记得告诉我哦!

  • 相关阅读:
    我最讨厌画图,这辈子我都不想再画图
    bzoj1218[HNOI2003]激光炸弹
    bzoj1196[HNOI2006]公路修建问题
    bzoj1588[HNOI2002]营业额统计
    bzoj2039[2009国家集训队]employ人员雇佣
    bzoj3874[Ahoi2014]宅男计划
    bzoj2282[Sdoi2011]消防
    bzoj1798[Ahoi2009]Seq 维护序列seq
    bzoj4003[JLOI2015]城池攻占
    bzoj2809[Apio2012]dispatching
  • 原文地址:https://www.cnblogs.com/zengyuanjun/p/7429968.html
Copyright © 2020-2023  润新知