装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
在一个web项目中,可能存在非常多的表单,如注册、登录、修改用户信息等。在表单数据提交给后台之前,常常要做一些校验,比如登录的时候需要验证用户名和密码是否为空,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <body> 用户名:<input id="username" type="text"/><br/> 密码:<input id="password" type="password" /> <br/> <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',param); } submitBtn.onclick = function(){ formSubmit(); } </script>
|
formSubmit函数在此处承担了两个职责,除了提交ajax请求之外,还要验证用户输入的合法性。这种代码一般会造成函数臃肿,职责混乱,二来谈不上任何可复用性。
本节的目的是分离校验输入和提交ajax请求的代码,我们把校验输入的逻辑放到validata函数中,并且约定当validata函数返回false的时候,表示校验未通过,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| var username = document.getElementById('username'), password = document.getElementById('password'), submitBtn = document.getElementById('submitBtn'); var validata = function(){ if(username.value === ''){ return alert('用户名不能为空'); } if(password.value === ''){ return alert('密码不能为空'); } } var formSubmit = function(){ if(validata() === false){ return; } var param = { username:username.value, password:password.value } ajax('http:xxx.com',param); } submitBtn.onclick = function(){ formSubmit(); }
|
现在的代码已经有了一些改进,我们把校验的逻辑都放到了validata函数中,但formSubmit函数的内部还要计算validata函数的返回值,因为返回值的结果表明了是否通过校验。
接下来进一步优化这段代码,使validata和formSubmit完全分离开来。首先改写Function.prototype.before,如果beforefn的执行结果返回false,表示不再执行后面的原函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| var username = document.getElementById('username'), password = document.getElementById('password'), submitBtn = document.getElementById('submitBtn'); Function.prototype.before = function(beforefn){ var _self = this; return function(){ if(beforefn.apply(this,arguments) === false){ return; } return _self.apply(this,arguments); } } var validata = function(){ if(username.value === ''){ return alert('用户名不能为空'); } if(password.value === ''){ return alert('密码不能为空'); } } var formSubmit = function(){ var param = { username:username.value, password:password.value } ajax('http:xxx.com',param); } formSubmit = formSubmit.before(validata); submitBtn.onclick = function(){ formSubmit() }
|
在这段代码中,校验输入和提交表单的代码完全分离开来,它们不再有任何耦合关系,validata成为一个即插即用的函数,它甚至可以被写成配置文件的形式,这有利于我们分开维护这两个函数,再利用策略模式稍加改造,我们就可以把这些校验规则都写成插件的形式,用在不同的项目当中。
值得注意的是,因为函数通过Function.prototype.before或者Function.prototype.after被装饰之后,返回的实际上是一个新的函数,如果在原函数上保存了一些属性,那么这些属性会丢失。代码如下:
1 2 3 4 5 6 7 8 9 10
| var fun = function(){ alert(1); } func.a = "a"; func = func.after.after(function(){ alert(2); }); alert(func.a);
|
另外,这种装饰方式也叠加了函数的作用域,如果装饰的链条过长,性能上也会受到一些影响。
本文选自:
《javascript设计模式与开发实践》