JavaScript设计模式笔记-装饰者模式

最常见的装饰者模式

比如我们要给onload增加新的方法,我们又不能改动内部的代码,如果改动了那么就违反了开放-封闭原则

window.onload=function(){ 
   alert(1) 
} 
var _onload=window.onload||function(){}; 
window.onload=function(){ 
   _onload(); 
   alert(2); 
} 

当然这个函数还是存在问题的

  • 必须要维护_onload这个中间变量,如果后续添加的方法越来越多,那么中间变量也就会越来越多
  • this丢失问题

比如我们把上述的代码换成document.getElementById

var _getElementById=document.getElementById; 

document.getElementById=function(id){ 
   alert(1) 
   return _getElementById(id) 
} 

很明显这段代码执行起来会有问题,因为这里的_getElementById(id)中this指向的并不是document这个对象而是全局的window那么我们可以用call来修复,但是这样做明显不方便

var _getElementById=document.getElementById; 

document.getElementById=function(id){ 
   alert(1) 
   return _getElementById.call(document,id) 
} 

于是就有一门新的方法 AOP面向切片

AOP装饰函数

首先来写Function.prototype.after和Function.prototype.before

Function.prototype.after=function(afterFn){ 
   var self=this //保存原函数引用 
   return function(){ 
       var ret=self.apply(this,arguments)//执行原函数 
       afterFn.apply(this,arguments)//执行新函数,并且保证this不会被劫持 
       return ret//返回原函数结果 
   } 
} 

Function.prototype.before=function(beforeFn){ 
   var self=this 
   return function(){ 
       beforeFn.apply(this,arguments) 
       var ret=self.apply(this,arguments) 
       return ret 
   } 
} 

上面的代码很简单.一个是先执行原函数一个是先执行新函数.下面来看一段代码

<button id="button">button</button> 
document.getElementById=document.getElementById.before(function(){alert(1)}); 
var button=document.getElementById("button") 

先会弹出一个1然后再获取到button.包括改写window.onload这个例子

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

如果不喜欢这种污染了原型的AOP那么这里可以给出通用的AOP

var before=function(fn,before){ 
   return function(){ 
       before.apply(this,arguments) 
       return fn.apply(this,arguments) 
   } 
} 

var a=before(function(){alert(3)},function(){alert(2)}) 
a=before(a,function(){alert(1)}) 

数据统计上报

当点击按钮后打开登陆浮窗,然后再进行数据上报

 
var showLogin=function(){ 
   console.log("打开登陆浮窗") 
} 

var log=function(){ 
   console.log("数据上报"+this.getAttribute("tag")) 
} 

showLogin.after(log) 

document.getElementById("button").onclick=showLogin 

AOP动态修改传递参数

比如我们需要在ajax的时候传递一个token参数,但是这个token非必须的,即有些链接不需要这个token参数.那么这里就可以用AOP来实现

var ajax=function(type,url,param){ 
   console.log(param) 
}; 

var getToken=function(){ 
   return "Token" 
} 

ajax=ajax.before(function(type,url,param){ 
   param.token=getToken() 
}) 

ajax("get","http://xxx.com/x",{name:"sven"}) 

这里的动态传递主要取决于AOP的这条语句

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

当我们使用AOP的时候实际上返回的就是这个function.而先执行before函数的时候 因为修改了param所以导致执行fn.apply(this,arguments)语句的时候arguments值也变了.

插件式表单验证

当我们点击表单按钮的时候验证是否通过.要避免那些大量的if else语句那么我们可以用装饰者模式,只需要修改一个地方

Function.prototype.before=function(beforeFn){ 
   var self=this 
   return function(){ 
       if(beforeFn.apply(this,arguments)===false){//如果验证不通过直接返回false 
           return 
       } 
       var ret=self.apply(this,arguments) 
       return ret 
   } 
} 

var validata=function(){ 
   if(username.value===""){ 
       alert("用户名不能为空") 
       return false 
   } 
   if(password.value===""){ 
       alert("密码不能为空") 
       return false 
   } 
} 

var formSubmit=function(){ 
   var param={ 
       username:username.value, 
       password:password.value 
   } 
   ajax("http://xxx.com/x",param) 
} 

formSubmit.before(validata)//提交之前先进行验证 

submitBtn.onclick=function(){ 
   formSubmit(); 
} 

AOP这个符合了开放-封闭原则,在不修改源代码的模式下如何去增加新的功能,但是要注意的是 如果你直接给function增加了一些属性,那些属性在AOP后是不存在的 例如

var func=function(){ 
   alert(1); 
} 
func.a="a" 
func=func.after(function(){ 
   alert(2) 
}) 
alert(func.a);//undefined 

总结

装饰者模式与代理模式比较想像,但是不同的是 代理模式一开始就确定了代理与本体的关系,装饰者模式是在不清楚本体是谁的情况下提供的一个代理.代理模式通常只有一层代理-本体引用,而装饰者模式经常会形成一条长长的装饰链.