使用AOP来改善JavaScript代码

在 javscript 中 AOP 的使用会带来很多方便,在程序的结构上实现了解耦,代码逻辑更加清晰。使用 AOP 我们还可以对逻辑进行非侵入性的改造,下面来看下用法:

用于非侵入的插入逻辑

before 函数

1
2
3
4
5
6
7
8
9
10
Function.prototype.before = function(func) {
var __self = this;
return function() {
if (func.apply(this, arguments) == false) {
return false;
}
return __self.apply(this, arguments);
}
}

after 函数

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.after = function(func) {
var __self = this;
return function() {
var ret = __self.apply(this, arguments);
if (ret === false) {
return false;
}
func.apply(this, arguments);
return ret;
}
}

考虑这种情形,我们需要在 onload 方法之后或者之前执行一些代码
比如,我们需要在 alert(1)之前 alert(0),在之后 alert(2)

1
2
3
window.onload = function() {
alert(1);
}

我们考虑如下三种手段:

  1. 直接修改 onload 中的代码
1
2
3
4
5
window.onload = function() {
alert(0);
alert(1);
alert(2);
}

无疑这种方式是耦合度最高,最具侵入性的方法。
2. 使用中间变量保存 onload

1
2
3
4
5
6
7
8
9
10
11
12
13
window.onload = function() {
alert(1);
}
var __onlaod = window.onload;

window.onload = function() {
alert(0);
if(__onlaod) {
__onload();
}
alert(1);
}

这种方式较上一种好一些,但是这里需要维护一个中间变量,维护中间变量还是需要一些成本的,而且代码冗长不够优雅也不够通用。
3. 使用 AOP 的方式

1
2
3
4
5
6
7
8
9
10
window.onload = function() {
alert(1);
};
window.onload = (window.onload || function(){}).before(function(){
alert(0);
});

window.onload = (window.onload || function(){}).after(function(){
alert(2);
});

使用 AOP 的方式显然是耦合度最低的非侵入式做法,代码也优雅很多,那我们来分析一下这两个函数的实现原理,以便今后我们可以自己写出这样的方法。

下面我们来逐行地分析一下这两个方法,首先来看 before 函数:

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
Function.prototype.before = function(func) {
var __self = this; //使用中间变量 __self 保存了 this 指针,此时的 this 指向调用者,也就是 onload 方法
return function() { //返回一个函数
if (func.apply(this, arguments) === false) {
//使用 apply 函数用 this 指针调用 func,因为这时候这个函数已经被赋值给 window.onload,所以这时候的 this 指向的是 window 对象,
//这里其实是 window.func(arguments);
//如果返回值是非 false 的返回值,就继续执行下一步,这个 false 是应该在 before 函数中返回的,用于控制是否继续执行原始的 onload 方法。
return false;
}
return __self.apply(this, arguments); //如果返回值不是 flase,这里就直接使用 this 指向的 window 对象调用保存的 onload 方法,实现对原始 window.onload 的调用
}
}

Function.prototype.after = function(func) {
var __self = this; //使用中间变量 __self 保存了 this 指针,此时的 this 指向调用者,也就是 onload 方法
return function() { //返回一个函数
var ret = __self.apply(this, arguments); //直接使用 this 指针指向的 window 对象调用 onload 方法,实现 window 对 onload 方法的原始调用。并取方法调用的返回值。
if (ret === false) { //如果onload 方法中没有返回 false,则说明,onload 方法中允许执行后续的方法。继续执行后续步骤
return false; //否则就终止方法调用并且返回 false;
}
func.apply(this, arguments); //这时的 this 指针指向 window 对象,使用 window 对象调用 func
//其实是 window.func(arguments);
return ret; //返回返回值
}
}

在本例中我们又一次看到了 ES 中 apply 的强大动态特性,很类似于 OC 中的 Swizzle
,动态的交换方法的实现,来实现非侵入的在方法的前后插入逻辑的动态装饰功能。

其实 AOP 实现的是一个拦截程序生命周期的方法,通过拦截程序的生命周期来对不同的功能实现解耦。它的实现依赖动态语言的运行时特性,通过使用这些特性来让代码变得更加优雅,可维护性更好。