aopjs

AOP of Nodejs

aopjs

为Nodejs设计的AOP实现,也可以用于其他AMD JavaScript 环境。

  1. 日志或认证/授权等常见AOP用途

关于 AOP

  1. 当你想改变所依赖的npm包的功能时,可以使用 aopjs 在代码层面 “切换” 它们的某些函数,变量,常量,而不必硬修改它们的源代码,从而避免依赖的包在升级后造成的版本冲突。

$ npm i aopjs
$ npm i aopjs -d
$ make test
./node_modules/.bin/mocha \
        --reporter list \
        --timeout 1000 \
        -u tdd \
        test/*.js
 
  ․ AOP # AOP.createCallableInstance: 5ms
  ․ AOP # AOP.compile.fundef: 20ms
  ․ AOP # AOP.createAdvisor: 2ms
  ․ AOP # AOP.for.function: 42ms
  ․ AOP # AOP.for.const: 37ms
  ․ joinpoints # JointFunctionDefine.match(): 14ms
  ․ joinpoints # JointVariable.match(): 6ms
  ․ joinpoints # JointConst.match(): 4ms
  ․ PathPattern # PathPattern.match: 5ms
  ․ util # util.localScopeByPos(): 6ms
 
  10 passing (149ms)
$ npm i aopjs -d
$ make debug

将打开一个支持断点和inspecte的nodejs内置的cl界面,然后自动执行aopjs所有的测试脚本。

如果你对aopjs的某些功能有疑问,make debug是个不错的调试方式,Fork this repository, fix it, and send me a request pull :)

这是一个简单的例子,b.js 文件对 a.js 文件进行aop操作:找到a.js里名为 foo 的函数,切入一个 around 类型的函数,在 around 函数里替换传递给 foo 的参数

a.js

// this is a.js 
exports.foo = function foo(who){
    return "hello " + who ;
}

b.js

// this is b.js 
var aop = require("aopjs") ;
 
aop("./a.js")
    .defun("foo") // <-- 找到了 a.js 文件里的 foo 方法 
    
    // 切入一个 around 类型的函数,由 around 函数调用原始函数 
    .around(function(who){
        return arguments.callee.origin("you") ;
    }) ;
    
var foo = require("./file-a.js").foo ;
console.log( foo("world") ) ;

执行结果会打印:

hello you

AOP = Aspect Oriented Programming (面向切面编程)

  1. 将系统里的代码定义为连接点(joinpoint/joint),aopjs 目前支持的 joinpoint 仅为:函数定义(before/around/after),常量(getter),变量(setter/getter)

  2. 按特性找到连接点(joinpoint),将有用的joinpoint集合在一起,形成切入点(pointcut)

  3. 对切入点(pointcut)进行函数切入(cut)操作,被切入到pointcut上的函数称为“方面”(adive);aopjs目前实现的advice类型有:before,around,after,setter,getter

API

aopjs用文件路径模板来匹配目标文件:

  • 绝对路径:

    var aop = require("aopjs") ;
    aop("/some/file/path") ;
  • 相对路径:

    aop("./some/file/path") ;

    aop("../some/file/path") ;
  • 使用“*”做为通配符:

    // 匹配folder目录下的所有 .js 后缀文件(不包括子目录下的文件!) 
    aop("/some/folder/*.js") ;
  • 使用“**”做为多级目录通配符:

    // 匹配folder及其子目录下的所有.js文件 
    aop("/some/folder/**.js") ;
  • 使用数组来传递多个文件路径模板:

    aop(["/some/file/path","/another/file/path"]) ;
  • namePattern 可以是一个字符串或正则表达式

    // a.js 文件中名为 foo 的函数
    aop("/some/folder/a.js").defun("foo") ;

    // folder目录下所有.js文件中,所有名称以 foo(忽略大小写) 开头的函数
    aop("/some/folder/*.js").defun(/^foo/i) ;

    只有关键词function后的部分被当做函数名称,将函数赋值给变量,变量名不会作为函数名称,例如:var foo = function (){ ... }无法用foo找到函数

  • 函数内部定义的函数需要先定位外层函数,例如:

    function foo(){
        function bar(){
            function baz(){
                function qux(){
                    // ... ... 
                }
            }
        }
    }

    找到最里面的 qux 需要像这样:

    aop("/some/file/path")
        .defun("foo")
        .defun("bar")
        .defun("baz")
        .defun("qux") ;

    任何类型的 连接点(joinpoint)都需要在scope里定位;每个函数都是一个scope,aop()返回的是目标文件顶层scope,因此,通常都是从 aop() 开始。

    也许以后我会写一个类似 jQuery 的 selector 来定位连接点...

  • 匿名函数也能被找到,用字符串 #n 表示匿名函数,n 从 0 开始计数;每个scope都是独立计数的。

    // #0 not foo 
    var foo = function (){
        // #0 
        var bar = (function (){
            return "bar" ;
        }) () ;
        
        // #1 
        var baz = (function (){
            return "baz" ;
        }) () ;
    }
     
    // #1 not qux 
    var qux = function (){
        // #0 (重新从0开始计数) 
        var quux = (function (){
            return "quux" ;
        }) () ;
    }

    定位上面文件中的3个内部匿名函数:

    var joint_foo = aop("...").defun("#0") ;
     
    // 第1个全局函数里的 第1个匿名函数 
    joint_foo.defun("#0") ;
     
    // 第1个全局函数里的 第2个匿名函数 
    joint_foo.defun("#1") ;
     
    // 第2个全局函数里的匿名函数 
    aop("...").defun("#1").defun("#0") ;

const()用于在函数或顶层scope中定位代码里的常量。

  • value 可以是:string, int, float 类型的值,或者匹配字符串常量的正则表达式

  • position 表示匹配 value的常量出现的位置,从0计数;缺省表示所有匹配的常量

function PI(){
    return 3.14125 ;
}
 
var foo = "hello" ;
var bar = "hello world" ;
// 仅仅匹配 "hello" 
aop("/some/file/path").const("hello") ;
 
// 匹配字符串 "hello" 和 "hello world" 
aop("/some/file/path").const(/^hello/) ;
 
// 只匹配字符串 "hello world" 
aop("/some/file/path").const(/^hello/,1) ;
 
// 匹配 PI函数的返回值浮点数 
aop("/some/file/path").defun("PI").const(3.14125) ;