javascript 实现AOP

你相信么,在JavaScript只需一个函数5行代码即可实现完整的面向方面AOP编程功能。这5行代码的功能包括:

1、无限层次的函数无害拦截

2、函数执行前拦截
3、检查函数的参数值
4、重新设定函数的参数值
5、函数执行后拦截
6、检查函数执行后的返回结果
7、重新设定函数的返回结果

虽然动态函数式语言的效率是一个存在的问题,但是对于它的高度灵活性,简直让人令人惊叹不已,剧赞。
这个小小的函数源自于和爱明兄的一次讨论:在javascript中不修改源代码的形式扩充新功能的完美方式。
至于为啥要不直接修改别人源代码库,你想,当你辛苦修改后,人家发布了一个新版本,你是不是又要重新
修改一次?如果修改的地方少还好,多了的话,嘿嘿!所以,不是你的东西,最好不要动,除非你确定要自
己完全维护所有的代码(如果你实在太闲)。

为了给开发人员一个榜样,俺不得不写一个patch样例,最开始俺写的patch方式是这样滴:
FCKEditingArea.prototype.FckMediaStart = FCKEditingArea.prototype.Start;
FCKEditingArea.prototype.Start =  function( html, secondCall )
{
  var sHeadExtra = '<link href="' + FCKConfig.PluginsPath + 'media/css/fck_media.css" rel="stylesheet" type="text/css" _fcktemp="true" />' ;
  html = html.replace( FCKRegexLib.HeadCloser, sHeadExtra + '$&' ) ;
  return this.FckMediaStart(html, secondCall);
}

俺觉得这种补丁方式是不太让我满意的

爱民兄提到他喜欢这样patch方式:
FCKEditingArea.prototype.Start = function( func ) {
  return function( html ) {
    html = html.replace( FCKRegexLib.HeadCloser, sHeadExtra + '$&' ) ;
    return func.apply(this, arguments);
  }
}(FCKEditingArea.prototype.Start);

这种方式的确不存在漏洞了,perfect,但是却把我彻底弄懵了,看得我头大啊!琢磨了好久才算明白过来。
不过从管理角度考虑,如果程序中到处都是这样的方式,那么将大大降低程序的可读性,增加维护代码的成本
(ps,要开发维护这样的代码,你不得不请JS高级程序员才行)。
为了限制匿名函数的滥用导致的可读性下降,俺写了一个Inject()函数的雏形(将匿名函数的使用限制在其中),
经过爱民兄修改,然后又是讨论,然后我们又修改,如此反复,达到这个最终版本(也许还不是,谁知道呢)。


好了,荣誉归于爱民,臭鸡蛋归于俺,闲话少说,看看代码吧,不算上注释,这个Inject函数正好5行。如果你清楚
的知道匿名函数的特点,那么这个函数,你就不难看懂,否则你就只能管用了。用法在注释的例子里,够简单吧。

function Inject( aOrgFunc, aBeforeExec, aAtferExec ) {
  return function() {
    if (typeof(aBeforeExec) == 'function') arguments = aBeforeExec.apply(this, arguments) || arguments;
    //convert arguments object to array
    var Result, args = [].slice.call(arguments);
    args.push(aOrgFunc.apply(this, args));
    if (typeof(aAtferExec) == 'function') Result = aAtferExec.apply(this, args);
    return (typeof(Result) != 'undefined')?Result:args.pop();
  }
}

使用新的Inject方式的代码如下:
FCKMediaProcessor.EditingArea_StartBefore = function ( html, secondCall )
{
  var sHeadExtra = '<link href="' + FCKConfig.PluginsPath + 'media/css/fck_media.css" rel="stylesheet" type="text/css" _fcktemp="true" />' ;
  html = html.replace( FCKRegexLib.HeadCloser, sHeadExtra + '$&' ) ;
  //在执行前修改了参数值,所以返回修改后的参数
  return arguments;
}
FCKEditingArea.prototype.Start =  Inject(FCKEditingArea.prototype.Start, FCKMediaProcessor.EditingArea_StartBefore);


ok,下面将继续增强 Inject的功能,将会把浪子提到的控制原函数是否执行加上。
本增强版本和上面的Inject函数的版本使用上略有差别,使用规范如下:

执行前调用(BeforeExec)
如果修改了参数值,必须这样返回修改的参数: return new Arguments(arguments);
如果没有返回值或者返回undefined那么正常执行,返回其它值表明不执行原函数,该值作为替代的原函数返回值。
执行后调用(AfterExec)
如果希望取得函数的返回值,必须在原参数表后面增加一个参数: result
如果希望知道原函数是否被执行,那么必须在 result参数的后面再增加一个参数: isDenied 该值为真表明没有执行原函数
如果希望修改函数的返回值,那么你只要简单的把修改后的值返回即可。

示例:假设我们要拦截一个 doTest 函数,修改它的参数值a,在原参数值上+1,并在它的函数返回值上也+1:
//定义测试函数 doTest
var doTest = function (a) {
  alert('dotest 运行中');
  return a;
};

function beforeTest(a) {
  alert('执行前参数: a='+a);
  a += 1;
  return new Arguments(arguments);
}

function afterTest(a, result, isDenied) {
//这里不会体现出参数a的改变,如果原函数改变了参数a。因为在js中所有参数都是值参。
  alert('执行后参数: a='+a+'; result='+result+';isDenied='+isDenied);
  return result+1;
}
 
//注入该doTest函数
doTest = Inject(doTest, beforeTest, afterTest);

//执行 doTest并显示结果
alert (doTest(2));

和爱民兄谈论后,修订后的新的Inject函数如下,这下功能全了,具体代码也不过十多行:


function Arguments(args) {
  //convert arguments object to array
  this.value = [].slice.call(args);
}
function Inject( aOrgFunc, aBeforeExec, aAtferExec ) {
  return function() {
    var Result, isDenied=false, args=[].slice.call(arguments);
    if (typeof(aBeforeExec) == 'function') {
      Result = aBeforeExec.apply(this, args);
      if (Result instanceof Arguments) //(Result.constructor === Arguments)
        args = Result.value;
      else if (isDenied = Result !== undefined)
        args.push(Result)
    }

    !isDenied && args.push(aOrgFunc.apply(this, args)); //if (!isDenied) args.push(aOrgFunc.apply(this, args));

    if (typeof(aAtferExec) == 'function')
      Result = aAtferExec.apply(this, args.concat(isDenied));
    else
      Result = undefined;

    return (Result !== undefined ? Result : args.pop());
  }
}

注:本文转自http://blog.sina.com.cn/s/blog_67b072ce0100ipza.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值