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"開頭的方法進行增強,從結果可以看到,加減法運算前都打印了參數,而且減法運算由於參數不正確,並沒有執行。冪運算不是我們希望增強的方法,它的參數沒有被打印出來。

對於增強方法中有回調函數的情況,我想到的是把原函數及其參數作為回調函數的參數進行傳遞,沒有發現什么更好方法,就不細說了。

 

最后,文章中有什么問題,或是大家有什么好的想法,記得告訴我哦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM