javascript 设计模式(9) —— 装饰者模式

装饰者函数 :可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起,这有助于我们编写一个松耦合和高复用性的系统。

为什么叫装饰者?

只是在原函数的外部添加了一些装饰,使执行原函数的同时也会执行新函数!
好处:在不改变原函数的基础上,添加一些新的行为

应用1 :数据统计上报

比如页面中有一个登录 button,点击这个 button 会弹出登录浮层,与此同时要进行数据上报,来统计有多少用户点击了这个登录 button:

 Function.prototype.after = function( afterfn ){ 
	 var __self = this; 
	 return function(){ 
		 var ret = __self.apply( this, arguments ); 
		 afterfn.apply( this, arguments ); 
		 return ret; 
	 } 
 }; 
 var showLogin = function(){ 
 	console.log( '打开登录浮层' ); 
 } 
 var log = function(){ 
 	console.log( '上报标签为: ' + this.getAttribute( 'tag' ) ); 
 } 
 showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据
 document.getElementById( 'button' ).onclick = showLogin; 
应用2:用AOP动态改变函数的参数

观察 Function.prototype.before 方法:

Function.prototype.before = function( beforefn ){ 
	 var __self = this; 
	 return function(){ 
	 	beforefn.apply( this, arguments ); // (1) 
	 	return __self.apply( this, arguments ); // (2) 
	 } 
}

从这段代码的(1)处 和(2)处可以看到,beforefn 和原函数__self 共用一组参数列表arguments,当我们在 beforefn 的函数体内改变 arguments 的时候,原函数__self 接收的参数列表自然也会变化。

var func = function( param ){ 
 	console.log( param ); // 输出: {a: "a", b: "b"} 
} 
func = func.before( function( param ){ 
 	param.b = 'b'; 
}); 
func( {a: 'a'} );

应用3:插件式的表单验证

在一个 Web 项目中,可能存在非常多的表单,如注册、登录、修改用户信息等。在表单数据提交给后台之前,常常要做一些校验,比如登录的时候需要验证用户名和密码是否为空。

<html> 
 <body> 
	用户名:<input id="username" type="text"/>
	密码: <input id="password" type="password"/> 
 	<input id="submitBtn" type="button" value="提交"> 
</body>
<script> 
var username = document.getElementById( 'username' ), 
 password = document.getElementById( 'password' ), 
 submitBtn = document.getElementById( 'submitBtn' ); 
 var formSubmit = function(){ 
	 if ( username.value === '' ){ 
	 	return alert ( '用户名不能为空' ); 
	 } 
	 if ( password.value === '' ){ 
	 	return alert ( '密码不能为空' ); 
	 } 
	 var param = { 
		 username: username.value, 
		 password: password.value 
	 } 
	 	ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略
} 
//点击提交按钮,执行
 submitBtn.onclick = function(){ 
 	formSubmit(); 
 } 
 </script>

formSubmit 函数在此处承担了两个职责,除了提交 ajax 请求之外,还要验证用户输入的合法性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。

本节的目的是分离校验输入和提交 ajax 请求的代码,我们把校验输入的逻辑放到 validata函数中,使使 validata 和 formSubmit 完全分离开来。首先要改写 Function. prototype.before,如果 beforefn 的执行结果返回 false,表示不再执行后面的原函数。

Function.prototype.before = function( beforefn ){ 
	 var __self = this; 
	 return function(){ 
		 if ( beforefn.apply( this, arguments ) === false ){ 
		 	// beforefn 返回 false 的情况直接 return,不再执行后面的原函数
		 	return; 
	 	} 
	 	return __self.apply( this, arguments ); 
 	} 
} 

//验证用户名、密码是否为空
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/login', param ); 
}

//在执行表单之前进行验证
formSubmit = formSubmit.before( validata ); 
submitBtn.onclick = function(){ 
  formSubmit(); 
}

在这段代码中,校验输入和提交表单的代码完全分离开来,它们不再有任何耦合关系,formSubmit = formSubmit.before( validata )这句代码,如同把校验规则动态接在 formSubmit 函数之前,validata 成为一个即插即用的函数,它甚至可以被写成配置文件的形式,这有利于我们分开维护这两个函数。再利用策略模式稍加改造,我们就可以把这些校验规则都写成插件的形式,
用在不同的项目当中。

值得注意的是,因为函数通过 Function.prototype.before 或者 Function.prototype.after 被装饰之后,返回的实际上是一个新的函数,如果在原函数上保存了一些属性,那么这些属性会丢失。代码如下:

var func = function(){ 
 	alert( 1 ); 
} 
func.a = 'a'; 
func = func.after( function(){ 
 	alert( 2 ); 
}); 
alert ( func.a ); // 输出:undefined

另外,这种装饰方式也叠加了函数的作用域,如果装饰的链条过长,性能上也会受到一些影响。

Function.prototype.before = function( beforefn ){ 
	 var __self = this; // 保存原函数的引用
	 return function(){ // 返回包含了原函数和新函数的"代理"函数
	 	beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
	 	// 也会被原封不动地传入原函数,新函数在原函数之前执行
	 	return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果,
	 	// 并且保证 this 不被劫持
	 } 
} 
Function.prototype.after = function( afterfn ){ 
	 var __self = this; 
	 return function(){ 
	 	var ret = __self.apply( this, arguments ); //执行原函数
	 	afterfn.apply( this, arguments ); //后执行函数
	 	return ret; 
	 } 
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值