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