在一个Web项目中,注册、登录、修改用户信息等功能的实现都离不开提交表单。 在将用户输入的数据交给后台之前,常常要做一些客户端力所能及的校验工作,比如注册的时候需要校验是否填写了用户名,密码的长度是否符合规定,等等。这样可以避免因为提交不合法数据而带来的不必要网络开销。 假设我们正在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑。 用户名不能为空。 密码长度不能少于6位。 手机号码必须符合格式。 5.6.1 表单校验的第一个版本 现在编写表单校验的第一个版本,可以提前透露的是,目前我们还没有引入策略模式。代码如下: <html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 请输入用户名:<input type="text" name="userName"/ > 请输入密码:<input type="text" name="password"/ > 请输入手机号码:<input type="text" name="phoneNumber"/ > <button>提交</button> </form> <script> var registerForm = document.getElementById( 'registerForm' ); registerForm.onsubmit = function(){ if ( registerForm.userName.value === '' ){ alert ( '用户名不能为空' ); return false; } if ( registerForm.password.value.length < 6 ){ alert ( '密码长度不能少于6位' ); return false; } if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){ alert ( '手机号码格式不正确' ); return false; } } </script> </body> </html> 这是一种很常见的代码编写方式,它的缺点跟计算奖金的最初版本一模一样。 registerForm.onsubmit函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的校验规则。 registerForm.onsubmit函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长度校验从6改成8,我们都必须深入registerForm.onsubmit函数的内部实现,这是违反开放—封闭原则的。 算法的复用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的校验,那我们很可能将这些校验逻辑复制得漫天遍野。 5.6.2 用策略模式重构表单校验 下面我们将用策略模式来重构表单校验的代码,很显然第一步我们要把这些校验逻辑都封装成策略对象: var strategies = { isNonEmpty: function( value, errorMsg ){ // 不为空 if ( value === '' ){ return errorMsg ; } }, minLength: function( value, length, errorMsg ){ // 限制最小长度 if ( value.length < length ){ return errorMsg; } }, isMobile: function( value, errorMsg ){ // 手机号码格式 if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ return errorMsg; } } }; 接下来我们准备实现Validator类。Validator类在这里作为Context,负责接收用户的请求并委托给strategy对象。在给出Validator类的代码之前,有必要提前了解用户是如何向Validator类发送请求的,这有助于我们知道如何去编写Validator类的代码。代码如下: var validataFunc = function(){ var validator = new Validator(); // 创建一个validator对象 /***************添加一些校验规则****************/ validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); validator.add( registerForm.password, 'minLength:6', '密码长度不能少于6位' ); validator.add( registerForm.phoneNumber, 'isMobile', '手机号码格式不正确' ); var errorMsg = validator.start(); // 获得校验结果 return errorMsg; // 返回校验结果 } var registerForm = document.getElementById( 'registerForm' ); registerForm.onsubmit = function(){ var errorMsg = validataFunc(); // 如果errorMsg有确切的返回值,说明未通过校验 if ( errorMsg ){ alert ( errorMsg ); return false; // 阻止表单提交 } }; 从这段代码中可以看到,我们先创建了一个validator对象,然后通过validator.add方法,往validator对象中添加一些校验规则。validator.add方法接受3个参数,以下面这句代码说明: validator.add( registerForm.password, 'minLength:6', '密码长度不能少于6位' ); registerForm.password为参与校验的input输入框。 'minLength:6'是一个以冒号隔开的字符串。冒号前面的minLength代表客户挑选的strategy对象,冒号后面的数字6表示在校验过程中所必需的一些参数。'minLength:6'的意思就是校验registerForm.password这个文本输入框的value最小长度为6。如果这个字符串中不包含冒号,说明校验过程中不需要额外的参数信息,比如'isNonEmpty'。 第3个参数是当校验未通过时返回的错误信息。 当我们往validator对象里添加完一系列的校验规则之后,会调用validator.start()方法来启动校验。如果validator.start()返回了一个确切的errorMsg 字符串当作返回值,说明该次校验没有通过,此时需让registerForm.onsubmit方法返回false来阻止表单的提交。 最后是Validator类的实现: var Validator = function(){ this.cache = []; // 保存校验规则 }; Validator.prototype.add = function( dom, rule, errorMsg ){ var ary = rule.split( ':' ); // 把strategy和参数分开 this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入cache var strategy = ary.shift(); // 用户挑选的strategy ary.unshift( dom.value ); // 把input的value添加进参数列表 ary.push( errorMsg ); // 把errorMsg添加进参数列表 return strategies[ strategy ].apply( dom, ary ); }); }; Validator.prototype.start = function(){ for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息 if ( msg ){ // 如果有确切的返回值,说明校验没有通过 return msg; } } }; 使用策略模式重构代码之后,我们仅仅通过“配置”的方式就可以完成一个表单的校验,这些校验规则也可以复用在程序的任何地方,还能作为插件的形式,方便地被移植到其他项目中。 在修改某个校验规则的时候,只需要编写或者改写少量的代码。比如我们想将用户名输入框的校验规则改成用户名不能少于4个字符。可以看到,这时候的修改是毫不费力的。代码如下: validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); // 改成: validator.add( registerForm.userName, 'minLength:10', '用户名长度不能小于10位' ); 5.6.3 给某个文本输入框添加多种校验规则 为了让读者把注意力放在策略模式的使用上,目前我们的表单校验实现留有一点小遗憾:一个文本输入框只能对应一种校验规则,比如,用户名输入框只能校验输入是否为空: validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' ); 如果我们既想校验它是否为空,又想校验它输入文本的长度不小于10呢?我们期望以这样的形式进行校验: validator.add( registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用户名不能为空' }, { strategy: 'minLength:6', errorMsg: '用户名长度不能小于10位' }] ); 下面提供的代码可用于一个文本输入框对应多种校验规则: <html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 请输入用户名:<input type="text" name="userName"/ > 请输入密码:<input type="text" name="password"/ > 请输入手机号码:<input type="text" name="phoneNumber"/ > <button>提交</button> </form> <script> /***********************策略对象**************************/ var strategies = { isNonEmpty: function( value, errorMsg ){ if ( value === '' ){ return errorMsg; } }, minLength: function( value, length, errorMsg ){ if ( value.length < length ){ return errorMsg; } }, isMobile: function( value, errorMsg ){ if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){ return errorMsg; } } }; /***********************Validator 类**************************/ var Validator = function(){ this.cache = []; }; Validator.prototype.add = function( dom, rules ){ var self = this; for ( var i = 0, rule; rule = rules[ i++ ]; ){ (function( rule ){ var strategyAry = rule.strategy.split( ':' ); var errorMsg = rule.errorMsg; self.cache.push(function(){ var strategy = strategyAry.shift(); strategyAry.unshift( dom.value ); strategyAry.push( errorMsg ); return strategies[ strategy ].apply( dom, strategyAry ); }); })( rule ) } }; Validator.prototype.start = function(){ for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ var errorMsg = validatorFunc(); if ( errorMsg ){ return errorMsg; } } }; /***********************客户调用代码**************************/ var registerForm = document.getElementById( 'registerForm' ); var validataFunc = function(){ var validator = new Validator(); validator.add( registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用户名不能为空' }, { strategy: 'minLength:6', errorMsg: '用户名长度不能小于10位' }]); validator.add( registerForm.password, [{ strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' }]); validator.add( registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: '手机号码格式不正确' }]); var errorMsg = validator.start(); return errorMsg; } registerForm.onsubmit = function(){ var errorMsg = validataFunc(); if ( errorMsg ){ alert ( errorMsg ); return false; } }; </script> </body> </html>