装饰者,英文名叫decorator. 所谓的"装饰",从字面可以很容易的理解出,就是给 土肥圆,化个妆,华丽的转身为白富美,但本体还是土肥圆。
说人话.
咳咳~
在js里面一切都是对象,而且函数就是一等对象。 在普通的Object中,我们可以很容易的添加属性或者其他方法,当然函数也不例外。 但是,这样做的后果就是,我们会不断的改变本体,就像把凤姐送去做整形手术一样。 当然,结果有好有坏,也许凤姐就是下一个angelababy,也许... 所以,为了我们代码的纯洁度(就算你是丑小鸭), 我们可以不动刀子的情况下,让你变得又白又美。
引用装饰
这个是装饰的初级阶段,就是抹点粉而已。 在js中,我们叫做引用装饰。
talk is cheap, show u code
//我们给jimmy函数额外添加其他的功能
var jimmy = function(){
console.log("jimmy");
}
var _jimmy = jimmy;
jimmy = function(){
_jimmy();
console.log("I love HuaHua");
}
jimmy();
这个的应用场景主要就是在多人协作和框架设计里面。比如,李冰岩已经使用了onload函数,但是,小舟又想使用onload函数。 这样会造成一个问题,如果小舟直接改动的话,他需要看的是李冰岩写的蜜汁代码,而且还要防止不会引起错误。这无疑是很困难的,所以在这里,可以使用引用装饰,来给onload在添加一层。
//这是小李的蜜汁代码
var xiaoLi = function(){
console.log("蜜汁代码");
}
window.onload = xiaoLi; //小李已经绑定好onload函数了
//接下来小舟需要改动onload代码
var fn = window.onload;
var xiaoZhou = function(){
fn();
conosle.log("整洁代码");
}
window.onload = function(){ //根据onload的特性,直接覆盖掉小李的code
xiaoZhou();
}
所以引用装饰的用处还是蛮大的。
大你妹啊~~
啊。。。。
(另一Me) 我们来分析一下,上面那个引用模式有什么弊端(好处已经都知道了).
首先,我们使用引用模式的时候,必定会添加一个多余的引用对象,比如上文的"fn".
而且随着你程序链的增加,中间对象一定会和你节点同等数量的。当然,起名我就不说了,关键是,一大堆无用的代码放在那里,感觉很不爽的。 所以,这里引入AOP的装饰模式.
AOP装饰
亲切,熟悉,完美。 我们见过AOP应该不止一次了,在职责链模式使用过,在迭代器模式使用过等等。使用这么多次,好像还没有对AOP做一些基本解释呢?所以,这里给大家咻咻.
AOP中文名叫面向切面编程。 先说一下这个名词,“面向”这词应该不用解释,关键"切面"是什么鬼。 如果大家做过sangwich,应该就知道,首先我们买来一块面包,需要将面包切开,然后在切面上面加上一些flavoring,比如蔬菜,火腿,培根之类的。 恩,对比js程序来说,一个程序链就相当于你买回来的面包,flavoring就是你想加的功能函数,如何将函数正确的放置在程序链中合适的位置,这就是AOP做的事情。
首先,再次将两个动态函数咻咻:
Function.prototype.after = function(fn){
var _this = this;
return function(){
var res = _this.apply(this,arguments);
fn.apply(this,arguments);
return res;
}
}
Function.prototype.before = function(fn){
var _this = this;
return function(){
fn.apply(this,arguments);
return _this.apply(this,arguments);
}
}
这两个函数的组合构成了js中AOP模式的精华.而AOP最常用的就是讲与业务逻辑无关的功能动态织入到主程序中。
talk is cheap , show u code
举个栗子吧: 使用AOP计算程序运行事件
//纯手写计算函数运行事件
function factorial(n) { //最基本的阶乘计算
if (n === 1) return 1;
return n * factorial(n - 1);
}
function calTime(n){
var start = new Date().getMilliseconds();
factorial(n);
console.log(new Date().getMilliseconds() - start+"ms");
}
calTime(1000);
可以得出耗费的时间,但是,如果还有其他的函数需要测试,那么这么做的意义并没有很大的价值。我们使用AOP进行重构。
function factorial(n) { //最基本的阶乘计算
if (n === 1) return 1;
return n * factorial(n - 1);
}
var calTime = (function(){
var start;
return function(){
if(!start){ //给开始时间赋值
start = new Date().getMilliseconds();
}else{
console.log(new Date().getMilliseconds()-start+"ms");
start = undefined;
}
}
})();
var calcu = factorial.before(calTime).after(calTime)(200);
这样很好的将计时功能从业务逻辑中提取出来,而且看着真的很有angelababy的味道诶.
使用AOP的时候需要注意一点就是,before&after执行完后,返回的结果都是第一个函数的内容。
var result = function(){
return 1;
}.before(function(){
return 2;
}).after(function(){
return 3;
});
console.log(result()); //1
我们大致的了解了AOP的用法和理论,可以针对于开头所说的例子进行重构.
window.onload = function(){
console.log("小李的蜜汁代码");
}
var fn = window.onload;
fn.before(function(){
console.log("整洁代码");
});
window.onload = fn;
看起来,比上面那个栗子清晰很多,而且使用before和after也十分利于代码的阅读。
实例讲解AOP装饰
上面那个例子,只能算是AOP装饰模式的一个不起眼的角落。 AOP引用的场景在js中,或者说在任何一门语言中都是大放光彩的。 在js中,"细粒度"对象是程序中复用性最高的对象,能把对象用细粒度的形式表示,那么AOP无疑是最佳的选择。
在写业务逻辑的时候,我们最大的问题就是判断逻辑,使用大量的if语句,而这都可以经过思考巧妙化解。比如,我在写购买课程的时候就会遇到一些逻辑。 只有当课程数目符合要求的时候,购买的效果才能有效.
按正常的业务逻辑编写
var buy = function(){
if(confirm()){ //验证购买信息是否合法
http.buyCourse('xxx'); //发起请求
}
}
var confirm = function(){
console.log("验证购买数量");
}
document.querySelector(".buy").addEventListener("click",function(){
buy();
},false);
使用AOP装饰模式重构后
var buy = function(){
http.buyCourse("xxx"); //给后台发起请求,验证
}
var confirm = function(){
console.log("验证购买数量");
}
var buy = buy.before(confirm);
document.querySelector(".buy").addEventListener("click",function(){
buy();
},false);
美美代码的 满满即视感!!!
不够,老板,再来份糖炒栗子~
好嘞~
这里我们只是获取函数的结果,那我们想直接干预传递的参数,可以吗?
当然可以。
我们研究一下,before的内部构造(after是一样的)
Function.prototype.before = function(fn){
var _this = this;
return function(){
fn.apply(this,arguments);
return _this.apply(this,arguments);
}
}
这里,由于arguments是引用类型,如果fn改变了arguments,则会反映到_this.apply的arguments也会发生改变。 而这个应用场景就是,给ajax的地址添加上你需要的参数。
在实际项目中,一开始的接口,就是一个普普通通的地址,发请求,然后获取参数。
http.ajax(url,type).then(...)
想这样的使用,但是某天,你的leader提高了要求等级,将地址后面都加上一个token参数,或者说一个口令的md5或sha1的计算值,我想,这尼玛工作量应该不小。
当然,我们可以直接将url进行传递。
var http = {
ajax1(url){
url += param.getToken();
sendAjax(url);
},
ajax2(url){
...
}
...
}
而且,万一哪天你的leader说,哎,这样做安全性还是不太高,要不加两个token混淆一下。
啊~啊~啊~啊~(混淆你妹啊,过不过年啦)
如果你继续这么写,我相信,年终奖是有的,但是,春运火车票你估计是摸不着了。
所以可以使用AOP进行动态织入。要知道,参数,我AOP也是可以动的。
function dealUrl(url){
url+=param.getToken();
}
http.ajax = http.ajax.before(dealUrl);
http.ajax("www.example.com"); //此时的Url = www.example.com?token=23jkfd3kjfdksjfkjds
而且,这个处理url函数,我也是可以扔到任意一个js文件里面复用的耶.
棒!!!
我AOP可以动你要的参数,而且,我还可以把我的结果给你是咻咻,如果我不让你执行,你永远也不会执行,哈哈哈哈~~~~
对不起,,上面那段是我意淫AOP说的。。。 其实AOP可以算是万能的配置工具,比如表单验证吧。 我们经常会把表单验证和表单结果发送耦合在一起。
像这样
var sendRes = function(){
if(user.userName === ""){
alert("用户名不能为空~");
return;
}else if(user.password === ""){
alert("密码不能为空~");
return;
}
http.sendUser("xxx"); //验证成功发送用户信息
}
一个函数里面同时含有了两个职责,一个验证一个发送用户信息。 所以我们现在的主要目的就是解耦。
回想一下,以前表单验证我们使用策略模式,解耦了验证,这里我们再次使用。
var sendRes = function(){
var detect = new Detect(); //策略者模式
detect.add(user.userName,["notEmpty"]);
detect.add(user.password,["notEmpty"]);
var notPass = detect.getResult();
if(notPass){ //如果没通过
console.log(notPass);
return;
}
http.sendUser("xxx"); //验证成功发送用户信息
}
可以使用上面那个验证,但是结果是,验证和策略模式还是在一起。我们再使用AOP进行解耦。首先我们得重构一下before函数
Function.prototype.before = function(fn){
var _this = this;
return function(){
var res = fn.apply(this,arguments); //值为Boolean,表示是否继续向下传递
if(res==="next"){ //如果返回不成立,则继续向下传递
return _this.apply(this,arguments);
}
return res;
}
}
看到这里,有些同学应该明白是怎么一回事了。没错,就是根据before里面验证的结果判断是否执行下个发送请求的功能函数。
当然,如果不想污染原型,你也可以自定义一个函数。
var before = function(beforeFn,fn){
return function(){
var res = beforeFn.apply(this,arguments);
if(res==="next"){
return fn.apply(this,arguments);
}
}
}
这样写也是可以的。
我们先按原型方式写,这样直观一点
var sendRes = function(){
http.sendUser("xxx"); //验证成功发送用户信息
}
sendRes = sendRes.before(function(){
var detect = new Detect(); //策略者模式
detect.add(user.userName,["notEmpty"]);
detect.add(user.password,["notEmpty"]);
var notPass = detect.getResult();
if(notPass){ //如果没通过
console.log(notPass);
}
return "next";
});
可以看出,验证那部分已经完全和发送用户信息的功能函数完全给解耦了。 这样不仅提高了函数的重用性,而且也让你的代码朝着“细粒度”方向大步前进.
辩证装饰者模式
其实,装饰者模式和职责链模式的形式是完全一样的,所以,他们的弊端也是类似的。链造的过长,对于性能来说就是一次rape.所以,还是那句话,不要为了模式而模式,没有万能的模式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。