面向切面编程(AOP)初涉

原文链接://www.greatytc.com/p/20381c6b9251

面向切面编程(AOP)是 Aspect Oriented Programming 的缩写,脱胎于函数式编程,是一种无侵入式的编程风格。

无侵入式编程

所谓无侵入式编程,就是在不修改原有代码的基础上,对原始代码进行一些功能拓展。常可以应用于诸如代码统计、日志打印、原始函数功能补充等场景中。

下面通过一些示例来展示这种编程风格在 JavaScript 中的应用。

举个吃饭的例子

这里举一个吃饭的例子,你正在做一个关于“吃”的项目,项目中你定义了一个eat函数:

function eat(){

console.log("我正在吃糖醋排骨,好香好香")

}

随后随着需求升级,PM 要求你完善整个吃饭的流程,一个比较完整的吃饭流程应该是:做饭、吃饭、刷碗。此处你只定义了“吃”的方法,新需求还要求你实现“做饭”和“刷碗”两项功能。

你可以直接对eat函数进行修改,在函数开头加入“做饭”的功能,在函数结尾加入“刷碗”的功能,但你知道这样是不好的,因为这种方式侵入了原始的eat函数,降低了代码的可维护性,也增加了出错的可能性。

不让修改原始的eat函数怎么办?此时就需要一种无侵入式的编程范式,也就是本文要介绍的 AOP 编程。

JavaScript 中 AOP 的实现

在 JavaScript 中实现 AOP 编程,主要是通过Function.prototype完成。此例中,我们可以在Function.prorotype上挂载一个before方法和after方法,分别在eat函数执行前后调用。

Function.prototype.before = function(fn){

fn && fn();

this()

}

Function.prototype.after = function(fn){

this()

fn && fn();

}

此时如果我们调用before函数:

eat.before(()=>{

console.log("我先去做饭")

});

输出结果为:

我先去做饭

我正在吃糖醋排骨,好香好香

同理,如果我们调用after函数:

eat.after(()=>{

console.log("我得去刷碗了")

});

输出结果为:

我正在吃糖醋排骨,好香好香

我得去刷碗了

现在,我们已经初步实现了一个 AOP 的编程模型。

实现链式调用

上面的代码有没有什么问题呢?在我看来,至少有两个问题:

同时调用before函数和after函数,会导致eat函数被调用两次

无法实现链式调用

上面的代码,如果只是单独调用before或after函数,没有什么问题。但新流程是需要我们同时调用before和after方法的:

eat.before(()=>{

console.log("我先去做饭")

});

eat.after(()=>{

console.log("我得去刷碗了")

});

下面是运行结果:

我先去做饭

我正在吃糖醋排骨,好香好香

我正在吃糖醋排骨,好香好香

我得去刷碗了

可见,eat函数被调用了两次,如果我们想在“做饭”之前在做点操作,比如“买菜”,会导致eat函数多次的调用。究其原因,是因为我们调用before或after函数时,会立即调用eat函数,我们可以对before函数和after函数做一些修改,让它们返回一个函数:

Function.prototype.before = function(fn){

return () => {

fn && fn();

this()

}

}

Function.prototype.after = function(fn){

return () => {

this()

fn && fn();

}

}

通过返回一个函数,实现了eat的延迟调用,同时由于返回值是一个函数,我们还可以对该返回值应用before和after函数,并实现链式调用,这得益于 JavaScript 的灵活性。

修改代码后,我们进行以下调用:

eat.before(()=>{

console.log("我先去做饭")

}).after(()=>{

console.log("我得去刷碗了")

})();

你可以把before和after作为一个包装器,类似于bind方法,都是绑定在Function .prototype上的方法,对调用这些函数的函数(this)进行一些包装,返回一个新的函数数,而原始的函数不受影响。

现在我们实现了一个具有一定扩展性的 AOP 编程模型,此例中,如果你想在“做饭”之前加上“买菜”功能,在“刷碗”后加上“看电视”功能,以及其他任意的功能,都可以很方便的添加:

eat.before(()=>{

console.log("我先去做饭")

}).after(()=>{

console.log("我得去刷碗了")

}).before(()=>{

console.log("我先去买菜")

}).after(()=>{

console.log("我去休息一下")

})();

运行结果:

我先去买菜

我先去做饭

我正在吃糖醋排骨,好香好香

我得去刷碗了

我去休息一下

函数返回值

经过一连串的链式调用后,怎么获取原始函数的返回值呢?如果我们的eat函数有个返回值:

function eat(){

console.log("我正在吃糖醋排骨,好香好香");

return {

"dish":"糖醋排骨"

}

}

要想获取原始函数的返回值,我们再对before和after做一些修改:

Function.prototype.before = function(fn){

return () => {

fn && fn();

const result = this();

return result;

}

}

Function.prototype.after = function(fn){

return () => {

const result = this();

fn && fn();

return result;

}

}

这里对before和after函数返回的函数增加了返回值,返回值就是调用该函数的函数(这里是eat)的返回值,这样一来,eat函数的返回值就可以层层进行传递下去了:

const retVal = eat.before(()=>{

console.log("我先去做饭")

}).after(()=>{

console.log("我得去刷碗了")

}).before(()=>{

console.log("我先去买菜")

}).after(()=>{

console.log("我去休息一下")

})();

console.log(retVal)

调用结果如下:

我先去买菜

我先去做饭

我正在吃糖醋排骨,好香好香

我得去刷碗了

我去休息一下

{ dish: '糖醋排骨' }

实例:代码统计

通过前面的介绍,我们实现了一个 AOP 的编程模型,尽管这个模型并不是尽善尽美的,但我们已经可以用来做一些事情了。我们来做一个实现代码统计的例子,通过代码统计,可以找出一些运行时间较长的函数,然后对其进行优化。这里我们准备对test函数进行代码统计:

function test(){

for(let i = 0; i < 100000000;i++){}

}

我们可以实现一个getRunTime方法,该方法用来进行代码统计:

function getRunTime(fn){

// 定义开始时间和结束时间

let start;

let end;

// 使用获取代码运行时间

fn.before(()=>{

start = +new Date();

}).after(()=>{

end = +new Date();

})();

return end - start;

}

然后只需调用getRunTime方法,就可以实现代码统计了:

const runTime = getRunTime(test)

console.log(runTime)

运行结果:

66

getRunTime的内部实现是依赖于 AOP 的,通过 AOP 实现了无侵入式的编程,我们在没有对test内部代码进行任何修改的情况下,实现了对其的运行时长进行统计,这种编程范式的优点,大家可以细细体会。

总结

本文对面向切面编程(AOP)的概念进行了一些介绍,并给出了 JavaScript 的实现方式,最后给出了一个例子。

在 JavaScript 中实现 AOP 的原理很简单,就是通过对Function.prototype进行扩展,这种方式也应用于诸多内置函数中,如常用的bind函数,我们应该主要学习这种思想。

在 Python 语言中,装饰器也是一种面向切面编程的范式,我在这篇文章中也进行了一些介绍,大家可以对比学习。

作者:黑黢黢

链接://www.greatytc.com/p/20381c6b9251

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容