AOP大家都知道,Javascript对于AOP的使用也有一些很好的地方.这几天正好在改别人代码他在javascript用了AOP进行编程,正好仔细说说怎么玩的这个.
AOP
单独AOP的概念最早接触是在Java中,面向切片编程SSH三大框架中的spring对它有深入的使用(当然,我根本没来得及深呢就投入伟大的前端事业了).
AOP如何面向切片编程的呢?
举一个很典型的例子,其实AOP有很多应用,这里感觉最典型的,在javascript中的就是check或者log,在操作之前的check以及操作之后的log.
比如有一个操作,是要发送一个请求保存一些数据:
1
2
3
4
5
6
7
8
9
10
11
|
// 点击按钮保存
function
btnClick(obj){
// 数据进行一定的初始化
var
saveObject = dateFormat(obj);
// 在数据进行保存之前,先check一下是否合法
beforeSaveCheck(saveObject);
// 真正的保存操作
save(saveObject);
// 保存之后存储一下日志
saveLog(saveObject);
}
|
可以看到,在真正的保存操作之前和之后会有一个check和log的过程,而很可能的是在每一个类似操作的函数中,都需要这两个步骤.如果每一个对应的操作都加上这两个函数,你认为科学吗?对于代码的维护以及未来的扩展都会带来很大程度上的不方便.
AOP在javascript中就可以很好地解决类似的问题,用一种更加有好的方式.让我们来畅想一下这种更好的方式,它会将非常零散杂乱,但是公共的部分组合在一起,并且实现高复用,低耦合.如果说面向对象的思想是在程度的程度设计上进行操刀,那么面向切片就是对方法的进一步升级和改造.
那么为AOP做一个浅显的总结就是在不修改原代码的情况下,动态添加功能的一种技术.
如果不使用AOP呢?
上面为AOP说了很多好话,但是光说好可能不会让大家了解它的好.我们再来看如果不使用它,一般情况下怎么处理一些问题.比如jquery的$.css()方法,可以设置样式.现在我们希望定义几个特殊的方法,比如:
1
2
3
4
|
$(
'#id'
).css(
'font-size'
,
'big'
);
$(
'#id'
).css(
'font-size'
,
'normal'
);
$(
'#id'
).css(
'font-size'
,
'small'
);
// 不需要再输入具体的字体大小值,big代表18px,normal代表14px,small代表8px
|
如果使用粗暴的手法,可以直接让原有代码支持我们新的功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(
function
($){
// 把原方法暂存起来:
var
_oldcss = $.fn.css;
// 重写原方法:
$.fn.css =
function
(prop,value){
if
(/^font-?size$/i.test(prop) && value.toLowerCase() ===
'big'
) {
return
_oldcss.call(
this
,prop,
'18px'
);
}
else
{
return
_oldcss.apply(
this
,arguments);
}
};
})(jQuery);
|
上面的代码虽然实现了,不过完全让人hold不住.为什么?第一修改了原来的代码,尤其是这种fix库的代码很不爽,尤其如果你代码洁癖比较重.另外也违反了'开放-封闭'的原则,没有对扩展开发,对修改封闭.
话归上题,还是来看看用AOP如何实现check和log的.
AOP的基本实现
来看一个简单地AOP实现:
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
|
var
AOP = {
around:
function
(pointcut,advice,namespaces) {
if
(namespaces === undefined || namespaces.length ===0 ) {
namespaces = [(
function
(){
return
this
;}).call()];
}
for
(
var
i
in
namespaces) {
var
ns = namespaces[i];
for
(
var
member
in
ns) {
if
(
typeof
ns[member] ===
'function'
&& member.match(pointcut)) {
(
function
(fn,fnName,ns){
ns[fnName] =
function
(){
return
advice.call(ns,{fn:fn,fnName:fnName,arguments:arguments});
}
})(ns[member],member,ns)
}
};
};
},
next:
function
(f){
return
f.fn.apply(
this
,f.arguments);
}
}
function
hello(name){
console.log(
'aop hello '
+ name);
}
hello(
'world'
);
AOP.around(
'hello.*'
,
function
(f){
console.log(
'before hello world'
);
AOP.next(f);
console.log(
'after hello world'
);
});
hello(
'world2'
);
|
输出:
1
2
3
4
|
aop hello world
before hello world
aop hello world2
after hello world
|
这是around的实现,基本的还有before和after的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Aop.before =
function
(pointcut, advice, namespaces) {
Aop.around(pointcut,
function
(f) {
advice.apply(
this
, f.arguments);
return
Aop.next(f)
},
namespaces);
};
Aop.after =
function
(pointcut, advice, namespaces) {
Aop.around(pointcut,
function
(f) {
var
ret = Aop.next(f);
advice.apply(
this
, f.arguments);
return
ret;
},
namespaces);
};
|