林秀栋的技术博客

前端解读面向切面编程(AOP)

AOP定义

第一步还是要知道aop是什么,先来个自维基百科的解释:

面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。 侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。

其从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来。

业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护。

这样原本分散在在整个应用程序中的变动就可以很好的管理起来。

tip

确实有点那么不太清晰,有点乱。不过在乱之前,我们可以选能理解的部分先看一下:

简而言之,AOP是针对业务处理过程中的切面(即非业务逻辑部分,例如错误处理,埋点,日志等)进行提取。

它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果(目的是降低耦合)。

具体到实现来说就是通过动态的方式将非主关注点部分插入到主关注点(一般是业务逻辑中)

埋点场景

假设一个场景,需要点击按钮之后进行信息上报。

假设我们有这么个logger的工具,可以进行上报:

    const logger = console.log
    //引入即可使用
    logger('按钮被点击了')

    const doSomething = () =>{
        console.log('doSomething')
    }
    let clickHandler = () =>{
        logger('doSomething之前')
        // n行代码
        doSomething()
        logger('doSomething之后')
        //n 行代码
    }

如果有30个按钮,每个业务逻辑不同,都需要埋这个点(假设打点信息一致)。那么都要手动写这个方法,非常不利于维护。

而这就是符合AOP的使用前提,那么试试AOP吧。

关注点划分

根据前面提的,可以划分下关注点。

主关注点 侧关注点
业务逻辑(doSomething) 埋点信息 logger

前面提到AOP关注的是步骤具体到例子来说其实就是插入logger的步骤。

插入时机无非时业务逻辑执行之前或者之后的阶段。

实现思路

具体到js来说,由于语言本身的特性,天生就具有运行时动态插入逻辑的能力。

重点在于在原函数上增加其他功能并不改变函数本身。

毕竟函数可以接受一切形式的参数,当然函数也不例外了。

当传入一个函数的时候,我们要对其操作的余地就很大了,

保存原函数,然后利用后续参数加上call或apply,就可以达到我们的目的。

此外为了给函数都增加一个属性,我们在原型上操作就行了。

经典before或者after的实现

// action 即为我们的侧关注点,即logger
Function.prototype.after = function (action) {
    //保留当前函数,这里this指向运行函数即clickHandler
    var func = this;
    // return 被包装过的函数,这里就可以执行其他功能了。
    // 并且该方法挂在Function.prototype上,
    // 被返回的函数依然具有after属性,可以链式调用
    return function () {
        // 原函数执行,这里不考虑异步
        var result = func.apply(this, arguments);
        // 执行之后的操作
        action.apply(this,arguments);
        // 将执行结果返回
        return result;
    };
};
// before 实现类似,只不过执行顺序差别而已
Function.prototype.before = function (action) {
    var func = this;
    return function () {
        action.apply(this,arguments);
        return func.apply(this, arguments);
    };
};

那么我们使用AOP改造之后的代码就如下了:

const doSomething = ()=>{
    console.log('doSomething')
}
let clickHandler = ()=>{
   // n行代码
   doSomething()
   //n 行代码
}
clickHandler = clickHandler.before(()=>{
     logger('doSomething之前')
}).after(()=>{
     logger('doSomething之后')
})
clickHandler() // 执行结果和预期一致

到这里就实现了面向切面编程,我们的业务逻辑里面只管业务本身,侧关注点通过这种方式来动态引入,与主逻辑解耦,更加纯净、易于维护。

参考文章

AllyTeam - 用AOP改善javascript代码

深入浅出 Javascript Decorators 和 AOP 编程