JavaScript中的AOP编程的基本实现

AOP 简介

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后, 再通过“动态织入”的方式掺入业务逻辑模块中。

面向切面编程给我们提供了一个方法,让我们可以在不修改目标逻辑的情况下,将代码注入到现有的函数或对象中。

虽然不是必须的,但注入的代码意味着具有横切关注点,比如添加日志功能、调试元数据或其它不太通用的但可以注入额外的行为,而不影响原始代码的内容。

给你举一个合适的例子,假设你已经写好了业务逻辑,但是现在你意识到没有添加日志代码。通常的方法是将日志逻辑集中到一个新的模块中,然后逐个函数添加日志信息。

然而,如果你可以获取同一个日志程序,在你想要记录的每个方法执行过程中的特定节点,只需一行代码就可将程序注入,那么这肯定会给你带来很多便利。难道不是吗?

切面、通知和切点(是什么、在何时、在何地)

为了使上面的定义更形式化一点,让我们以日志程序为例,介绍有关 AOP 的三个概念。如果你决定进一步研究这个范式,这些将对你有所帮助:

  • 切面 (是什么): 这是你想要注入到你的目标代码的 “切面” 或者行为。在我们的上下文环境(JavaScript)中,这指的是封装了你想要添加的行为的函数。
  • 通知 (在何时): 你希望这个切面什么时候执行?“通知” 指定了你想要执行切面代码的一些常见的时刻,比如 “before”、“after”、“around”、“whenThrowing” 等等。反过来,它们指的是与代码执行相关的时间点。对于在代码执行后引用的部分,这个切面将拦截返回值,并可能在需要时覆盖它。
  • 切点 (在何地): 他们引用了你想要注入的切面在你的目标代码中的位置。理论上,你可以明确指定在目标代码中的任何位置去执行切面代码。实际上这并不现实,但你可以潜在地指定,比如:“我的对象中的所有方法”,或者“仅仅是这一个特定方法”,或者我们甚至可以使用“所有以 get_ 开头的方法”之类的内容。

有了这些解释,你会发现创建一个基于 AOP 的库来向现有的基于 OOP 的业务逻辑(举个例子)添加日志逻辑是相对容易的。你所要做的就是用一个自定义函数替换目标对象现有的匹配方法,该自定义函数会在适当的时间点添加切面逻辑,然后再调用原有的方法。

基本实现

因为我是一个视觉学习者,所以我认为,展示一个基本的例子说明如何实现一种 切面 方法来添加基于 AOP 的行为将是个漫长的过程。

下面的示例将阐明实现它有多容易以及它给你的代码带来的好处。

`/** 用于获取一个对象中所有方法的帮助函数 */ const getMethods = (obj) => Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(item => typeof obj[item] === 'function')

/** 将原始方法替换为自定义函数,该函数将在通知指示时调用我们的切面 */ function replaceMethod(target, methodName, aspect, advice) { const originalCode = target[methodName] target[methodName] = (...args) => { if(["before", "around"].includes(advice)) { aspect.apply(target, args) } const returnedValue = originalCode.apply(target, args) if(["after", "around"].includes(advice)) { aspect.apply(target, args) } if("afterReturning" == advice) { return aspect.apply(target, [returnedValue]) } else { return returnedValue
 } } }

module.exports = { // 导出的主要方法:在需要的时间和位置将切面注入目标 inject: function(target, aspect, advice, pointcut, method = null) { if(pointcut == "method") { if(method != null) { replaceMethod(target, method, aspect, advice)
} else { throw new Error("Tryin to add an aspect to a method, but no method specified") } } if(pointcut == "methods") { const methods = getMethods(target) methods.forEach( m => { replaceMethod(target, m, aspect, advice)
}) } } }`

非常简单,正如我提到的,上面的代码并没有涵盖所有的用例,但是它应该足以涵盖下一个示例。

但是在我们往下看之前,注意一下这个 replaceMethod 函数,这就是“魔法”生效的地方。它能够创建新函数,也可以决定我们何时调用我们的切面以及如何处理它的返回值。

接下来说明这个库的用法:

`const AOP = require("./aop.js")

class MyBussinessLogic {
add(a, b) {
    console.log("Calling add")
    return a + b
}

concat(a, b) {
    console.log("Calling concat")
    return a + b
}

power(a, b) {
    console.log("Calling power")
    return a ** b
}
}

const o = new MyBussinessLogic()

function loggingAspect(...args) { console.log("== Calling the logger function ==") console.log("Arguments received: " + args) }

function printTypeOfReturnedValueAspect(value) { console.log("Returned type: " + typeof value) }

AOP.inject(o, loggingAspect, "before", "methods") AOP.inject(o, printTypeOfReturnedValueAspect, "afterReturning", "methods")

o.add(2,2) o.concat("hello", "goodbye") o.power(2, 3)`

这只是一个包含三个方法的基本对象,没什么特别的。我们想要去注入两个通用的切面,一个用于记录接收到的属性,另一个用于分析他们的返回值并记录他们的类型。两个切面,两行代码(并不需要六行代码)。

这个示例到这里就结束了,这里是你将得到的输出:

https://camo.githubusercontent.com/f18ef187f4acddab8df097c8aa4521d632e17759bc1c0831a22ada934388d7b5/68747470733a2f2f63646e2d696d616765732d312e6d656469756d2e636f6d2f6d61782f323030302f312a394b5a42774f6262714145754a4176314757537279672e706e67

AOP 的优点

在知道了 AOP 的概念及用途后,也行你已经猜到了为什么人们会想要使用面向切面编程,不过还是让我们做一个快速汇总吧:

  • 封装横切关注点的好方法。我非常喜欢封装,因为它意味着更容易阅读和维护可以在整个项目中重复使用的代码。
  • 灵活的逻辑。在注入切面时,围绕通知和切入点实现的逻辑可以为你提供很大的灵活性。反之这又有助于你动态地打开和关闭代码逻辑的不同切面(有意的双关)。
  • 跨项目重复使用切面。你可以将切面视为组件,即可以在任何地方运行的小的、解耦的代码片段。如果你正确地编写了切面代码,就可以轻松地在不同的项目中共享它们。

AOP 的主要问题

因为并非每件事都是完美的,这种范式遭到了一些批评者的反对。

他们提出的主要问题是,它的主要的优势实际上隐藏了代码逻辑和复杂性,在不太清楚的情况下可能会产生副作用。

如果你仔细想想,他们说的有一定道理,AOP 给了你很多能力,可以将无关的行为添加到现有的方法中,甚至可以替换它们的整个逻辑。当然,这可能不是引入此范式的确切原因,而且它肯定不是我上面提供的示例的意图。

然而,它确实可以让你去做任何你想做的事情,再加上缺乏对良好编程实践的理解,可能会导致非常大的混乱。

为了不让自己听起来太老套,我转述一下 Uncle Ben 的话:

能力越大,责任越大

如果你想正确地使用 AOP ,那么就必须理解软件开发的最佳实践。

在我看来,仅仅因为你使用这个工具之后可能会带来很大的损害,并不足以说明这个工具就是不好的,因为它也会带来很多的好处(即你可以将很多常见的逻辑提取到一个集中的位置,并可以在你需要的任何地方用一行代码注入它)。对我来说,这是一个强大的工具,值得学习,也绝对值得使用。

面向切面编程是 OOP 的完美补充,特别是得益于 JavaScript 的动态特性,我们可以非常容易地实现它(如这里的代码演示)。它提供了强大的功能,能够对大量逻辑进行模块化和解耦,以后甚至可以与其他项目共享这些逻辑。

当然,如果你不正确地使用它,你会把事情搞得一团糟。但是你绝对可以利用它来简化和清理大量的代码。这就是我对 AOP 的看法,你呢?你曾经听说过 AOP 吗?你以前使用过它吗?请在下面留言并分享你的想法!

到此这篇关于JavaScript中的AOP编程的文章就介绍到这了,更多相关js AOP编程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Javascript aop(面向切面编程)之around(环绕)分析

    Aop又叫面向切面编程,其中"通知"是切面的具体实现,分为before(前置通知).after(后置通知).around(环绕通知),用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点.但是利用aop可以有效的改善js代码逻辑,比如前端框架dojo和yui3中AOP则被提升至自定义事件的一种内在机制,在源码中随处可见.得益于这种抽象使得dojo的自定义事件异常强大和灵活.dojo中aop的实现在dojo/aspect模块中,主要有三个方法:before.

  • JavaScript AOP编程实例

    本文实例讲述了JavaScript AOP编程.分享给大家供大家参考.具体如下: /* // aop({options}); // By: adamchow2326@yahoo.com.au // Version: 1.0 // Simple aspect oriented programming module // support Aspect before, after and around // usage: aop({ context: myObject, // scope contex

  • JavaScript之AOP编程实例

    本文实例讲述了JavaScript之AOP编程.分享给大家供大家参考.具体如下: /* // aop({options}); // By: adamchow2326@yahoo.com.au // Version: 1.0 // Simple aspect oriented programming module // support Aspect before, after and around // usage: aop({ context: myObject, // scope contex

  • JavaScript实现AOP详解(面向切面编程,装饰者模式)

    什么是AOP? AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计.安全控制.异常处理等.把这些功能抽离出来之后, 再通过"动态织入"的方式掺入业务逻辑模块中. AOP能给我们带来什么好处? AOP的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块. JavaScript实现AOP的思路? 通常,在 JavaScript 中实现 AOP,都是指把一个函数"动态织入&qu

  • JavaScript中的AOP编程的基本实现

    AOP 简介 AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计.安全控制.异常处理等.把这些功能抽离出来之后, 再通过"动态织入"的方式掺入业务逻辑模块中. 面向切面编程给我们提供了一个方法,让我们可以在不修改目标逻辑的情况下,将代码注入到现有的函数或对象中. 虽然不是必须的,但注入的代码意味着具有横切关注点,比如添加日志功能.调试元数据或其它不太通用的但可以注入额外的行为,而不影响原始代码的内容. 给你举一个合适的

  • JavaScript中的函数式编程详解

    函数式编程 函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变,与函数式编程相对的是命令式编程.我们有这样一个需求,给数组的每个数字加一: // 数组每个数字加一, 命令式编程 let arr = [1, 2, 3, 4]; let newArr = []; for(let i = 0; i < arr.length; i++){ newArr.push(arr[i] + 1); } console.log(newArr)

  • JavaScript中的对象化编程

    关于对象化编程的语句 现在我们有实力学习以下关于对象化编程,但其实属于上一章的内容了. with 语句 为一个或一组语句指定默认对象. 用法: with (<对象>) <语句>; with 语句通常用来缩短特定情形下必须写的代码量.在下面的例子中,请注意 Math 的重复使用: x = Math.cos(3 * Math.PI) + Math.sin(Math.LN10); y = Math.tan(14 * Math.E); 当使用 with 语句时,代码变得更短且更易读: wi

  • Javascript中的异步编程规范Promises/A详细介绍

    Javascript里异步编程逐渐被大家接受,先前大家一般通过回调嵌套,setTimeout.setInterval等方式实现,代码看起来非常不直观,不看整个代码逻辑很难快速理解.Javascript里异步函数大概有I/O函数(Ajax.postMessage.img load.script load等).计时函数(setTimeout.setInterval)等. 这些我们都很熟悉,在复杂的应用中往往会嵌套多层,甚至以为某些步骤未完成而导致程序异常,最简单的例子:比如你往DOM中注入节点,你必

  • 体验Java 1.5中面向(AOP)编程

    对于一个能够访问源代码的经验丰富的Java开发人员来说,任何程序都可以被看作是博物馆里透明的模型.类似线程转储(dump).方法调用跟踪.断点.切面(profiling)统计表等工具可以让我们了解程序目前正在执行什么操作.刚才做了什么操作.未来将做什么操作.但是在产品环境中情况就没有那么明显了,这些工具一般是不能够使用的,或最多只能由受过训练的开发者使用.支持团队和最终用户也需要知道在某个时刻应用程序正在执行什么操作. 为了填补这个空缺,我们已经发明了一些简单的替代品,例如日志文件(典型情况下用

  • JavaScript中实现异步编程模式的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯:坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他

  • 详解在Javascript中进行面向切面编程

    面向切面编程(Aspect-oriented programming,AOP)是一种编程范式.做后端 Java web 的同学,特别是用过 Spring 的同学肯定对它非常熟悉.AOP 是 Spring 框架里面其中一个重要概念.可是在 Javascript 中,AOP 是一个经常被忽视的技术点. 场景 假设你现在有一个牛逼的日历弹窗,有一天,老板让你统计一下每天这个弹窗里面某个按钮的点击数,于是你在弹窗里做了埋点: 过了一个星期,老板说用户反馈这个弹窗好慢,各种卡顿.你想看一下某个函数的平均执

  • 在JavaScript中实现类的方式探讨

    在 javascript 中有很多方式来创建对象,所以创建对象的方式使用起来非常灵活.那么,到底哪一种方式是最恰当的对象创建方式呢?构造模式,原型模式还是对象原意模式(Object literal)呢? 但这些模式具体是怎么回事呢? 在开始讲解之前,让我们先清楚地介绍一下关于 javascript 基本知识. 有没有可能在 javascript 中实现面向对象编程的方式呢? 答案是可能的,javascript 是可以创建对象的!这种对象可以包含数据及能够操作数据的方法,甚至可以包含其他对象.它没

  • 面向对象的编程思想在javascript中的运用上部

    其实,面向对象的思想是独立于编程语言的,例如在C#中,在一个静态类的静态方法中,按照过程式开发调用一系列静态函数,我们很难说这是面向对象的编程,相反,象jquery和extjs这样优秀的javascript库,却处处体现着面向对象的设计思想.本文不打算探讨javascript是否能够算做面向对象的编程语言,这个问题是重视中国式考试的人应该关注的,我这里只是简单的说明如何在javascript中使用面向对象的编程思想. 面向对象首先要有对象.在javascript中创建一个对象非常简单: 复制代码

  • JavaScript中的DSL元编程介绍

    在看JavaScript Template源码的时候,发现有一个很有意思的用法用来生成函数,想到这不就是元编程么? JavaScript 元编程 复制代码 代码如下: 元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作. JavaScript eval 复制代码 代码如下: eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码. 可以有下面的用法: 复

随机推荐