Skip to content
On this page

19. AOP 在 JS 中的实现

定义

定义:AOP( Aspect-oriented programming)是面向切面编程,和 OOP、FP一样,都是一种编程思想,将一些与函数的核心功能无关的代码抽离出来,并能够添加一层通知机制,对分离出来的代码进行统一管理。

作用:很好的遵循 单一职责原则,该思想使得我们能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性,提高模块化程度。

实现

实现思路:我们要实现的效果大致如下所示:

js
// 主业务函数
function main() {
  console.log('执行主业务函数')
}

/** 
 * 对 main 进行切面的相关处理
 */

main()

/**
 * 输出:
 * 切入之前
 * 执行主业务函数
 * 切入之后
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在知道 AOP 之前,我们大概率会这么做:

js
function main() {
  console.log('切入之前')
  console.log('我是主业务函数')
  console.log('切入之后')
}
1
2
3
4
5

或者将函数抽离出来:

js
function before() {
  console.log('切入之前')
}

function after() {
  console.log('切入之前')
}

function main() {
  before()
  console.log('执行主业务函数')
  after()
}
1
2
3
4
5
6
7
8
9
10
11
12
13

但是上述方法都不能将 beforeafter 这些与主业务功能无密切关系的代码分离出来,因为 beforeafter 可能会用到 main 函数的处理结果,所以又不能直接写成下面这样:

js
before()
main()
after()
1
2
3

这个时候 AOP 就排上用场了,使用 JS 的原型、闭包和高阶函数,我们可以很方便的实现 AOP。

js
Function.prototype.before = function (beforeFunc) {
  let that = this // 保存主业务函数的 this
  return function () {
    beforeFunc() // 切入主函数之前
    that.apply(this, arguments) // arguments 是主函数中的
  }
}

Function.prototype.after = function (afterFunc) {
  let that = this // 保存主业务函数的 this
  return function () {
    that.apply(this, arguments) // arguments 是主函数中的
    afterFunc.apply() // 切入主函数之后
  }
}

function add(...args) {
  // 主函数:数字求和
  let res = args.reduce((pre, cur) => pre + cur, 0)
  console.log('数字之和:' + res)
  return res
}

function beforeFunc() {
  // 抽离出来的函数
  console.log('切入前')
}

function afterFunc() {
  // 抽离出来的函数
  console.log('切入后')
}

// 由于 before、after 返回的还是函数,所以可以链式执行
const main = add.before(beforeFunc).after(afterFunc)

main(1, 2)

/**
 * 打印:
 * 切入前
 * 数字之和:3
 * 切入后
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

改进

直接在 Function 原型上定义方法,一定程度上污染了原型,我们可以使用函数重新改写该方法。

js
function before(fn, beforeFunc) {
  return function () {
    beforeFunc()
    fn.apply(this, arguments)
  }
}

function after(fn, afterFunc) {
  return function () {
    fn.apply(this, arguments)
    afterFunc()
  }
}

function add(...args) {
  let res = args.reduce((pre, cur) => pre + cur, 0)
  console.log('数字之和为:' + res)
  return res
}

function beforeFunc() {
  console.log('切入之前')
}

function afterFunc() {
  console.log('切入之后')
}

add = after(before(add, beforeFunc), afterFunc)

add(1, 2, 3)

/**
 * 打印:
 * 切入之前
 * 数字之和为:6
 * 切入之后
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38