装饰者,英文名叫decorator. 所谓的"装饰",从字面能够很容易的理解出,就是给 土肥圆,化个妆,华丽的转身为白富美,但本体仍是土肥圆。 ajax
说人话.
咳咳~编程
在js里面一切都是对象,并且函数就是一等对象。 在普通的Object中,咱们能够很容易的添加属性或者其余方法,固然函数也不例外。 可是,这样作的后果就是,咱们会不断的改变本体,就像把凤姐送去作整形手术同样。 固然,结果有好有坏,也许凤姐就是下一个angelababy,也许... 因此,为了咱们代码的纯洁度(就算你是丑小鸭), 咱们能够不动刀子的状况下,让你变得又白又美。安全
这个是装饰的初级阶段,就是抹点粉而已。 在js中,咱们叫作引用装饰。app
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中文名叫面向切面编程。 先说一下这个名词,“面向”这词应该不用解释,关键"切面"是什么鬼。 若是你们作过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 codethis
举个栗子吧: 使用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引用的场景在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.因此,仍是那句话,不要为了模式而模式,没有万能的模式。