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

测试结果如下:

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

测试结果:

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

View Result

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

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

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

时间: 2024-08-20 05:38:42

JS中AOP的实现和运用的相关文章

js 实现 aop

Aop又叫面向切面编程,用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点,这篇就通过下面这几个小例子,来说说AOP在js中的妙用. 1, 防止window.onload被二次覆盖.2,无侵入的统计代码.3, 分离表单请求和校验.4,给ajax请求动态添加参数.5,职责链模式.6, 组合代替继承. 先给出before和after这2个“切面”函数. 顾名思义,就是让一个函数在另一个函数之前或者之后执行,巧妙的是,before或者after都可以和当前的函数公用th

js中获取时间new date()的用法

js中获取时间new date()的用法 获取时间:   var myDate = new Date();//获取系统当前时间 获取特定格式的时间: 1 myDate.getYear(); //获取当前年份(2位) 2 myDate.getFullYear(); //获取完整的年份(4位,1970-????) 3 myDate.getMonth(); //获取当前月份(0-11,0代表1月) 4 myDate.getDate(); //获取当前日(1-31) 5 myDate.getDay();

JS中的运算符&amp;JS中的分支结构

一.JS中的运算符 1.算术运算(单目运算符) + 加.- 减.* 乘./ 除.% 取余.++ 自增.-- 自减 >>> +:有两种作用,连接字符串/加法运算.当+两边全为数字时,进行加法运算: 当+两边有任意一边为字符串时,起连接字符串的作用,连接之后的结果为字符串 除+外,其余符号运算时,会先尝试将左右变量用Number函数转为数字 >>> /: 结果会保留小数点 >>> ++: 自增运算符,将变量在原有基础上+1: --: 自减运算符,将变量在原

Js中的数据属性和访问器属性

Js中的数据属性和访问器属性 在javaScript中,对象的属性分为两种类型:数据属性和访问器属性. 一.数据属性 1.数据属性:它包含的是一个数据值的位置,在这可以对数据值进行读写. 2.数据属性包含四个特性,分别是: configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性,默认为true enumerable:表示能否通过for-in循环返回属性 writable:表示能否修改属性的值 value:包含该属性的数据值.默

在Node.js中使用RabbitMQ系列二 任务队列

在上一篇文章在Node.js中使用RabbitMQ系列一 Hello world我有使用一个任务队列,不过当时的场景是将消息发送给一个消费者,本篇文章我将讨论有多个消费者的场景. 其实,任务队列最核心解决的问题是避免立即处理那些耗时的任务,也就是避免请求-响应的这种同步模式.取而代之的是我们通过调度算法,让这些耗时的任务之后再执行,也就是采用异步的模式.我们需要将一条消息封装成一个任务,并且将它添加到任务队列里面.后台会运行多个工作进程(worker process),通过调度算法,将队列里的任

js 中arguments的应用

在js中每个函数类都有一个Arguments对象实例arguments,.首先他不是一个数组,可以说算一个伪数组,但是用数组的索引形式也能获取到他的值,如 let len = arguments.length;表示函数参数的个数 arguments[0]表示第一个参数 在函数中使用它的好处就是这个函数在多处调用且参数有不是必须传递的,这样可以利用arguments来处理,解决了不需要的参数也要传递的问题 在实际应用中分装的函数中使用了一下,有不对的和需要改进的地方,还请大家多多指教,一起学习 e

js中,实现css格式的改变

js中,实现属性值的改变 (1)prop属性实现,html中标签的class属性值发生改变: 语法:$(元素标识).prop("class",类属性值); 例子:$("#num_"+currentId).prop("class","no-selected"); 类似的改变class属性: $("#index_" + index).removeClass("current"); $(&qu

JS中的循环结构、循环嵌套以及函数介绍

[循环结构的步骤]    *①声明循环变量    *②判断循环条件    *③执行循环体(while的{}中的所有代码)操作     *④更新循环变量    *     * 然后,循环执行②③④    *     *     * [JS中循环条件支持的数据类型]    * ①boolean:true 真     false   假    * ②string: 非空字符串为真       空字符串为假    * ③null/NaN/undefined:  全为假    * ④object:全为真 

关于js中window.location.href,location.href,parent.location.href,top.location.href的用法

关于js中window.location.href,location.href,parent.location.href,top.location.href的用法 "window.location.href"."location.href"是本页面跳转. "parent.location.href" 是上一层页面跳转. "top.location.href" 是最外层的页面跳转. 举例说明: 如果A,B,C,D都是html,D