您的位置:首页 > Web前端 > JavaScript

利用策略模式与装饰模式扩展JavaScript表单验证功能

2017-02-15 08:57 477 查看

简单的表单验证

html结构

<!-- validata.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Validata</title>
</head>
<body>
<form id="form">
<label for="username">账号:</label><input id="username" type="text"><br>
<label for="password">密码:</label><input id="password" type="password"><br>
<label for="phonenum">手机:</label><input id="phonenum" type="text"><br>
<input id="submit" type="button" value="提交">
</form>
<p id="warn"></p>
<script src="validata.js"></script>
</body>
</html>




首先先简单地实现以下这个功能

之后再用设计模式丰满

// validata.js
var form = document.getElementById('form'),
warn = document.getElementById('warn');
var validata = function(){
if(form.username.value === ''){
return warn.textContent = '账号不能为空';
}
if(form.password.value === ''){
return warn.textContent = '密码不能为空';
}
if(form.phonenum.value === ''){
return warn.textContent = '手机号不能为空';
}
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg);  ajax提交数据略
return warn.textContent = '用户信息已成功提交至服务器';
}
form.submit.onclick = function(){
validata();
}

然后分析以下代码

validata这个函数毫无复用性可言,除此之外存在两个问题

函数同时承担了验证和提交两个职责,违背单一职责原则
验证功能扩展性差,要想添加验证规则就必须深入函数内部,违反开放-封闭原则
所以我们需要对此进行改进

装饰模式重构

先来用装饰模式解决一下函数多职责问题

方法也很简单

改进一下AOP前置装饰函数(Function.prototype.before)

当扩展函数(beforeFn)返回false则不执行当前函数

然后令表单验证函数成为表单提交函数的前置装饰

这样提交前就会进行验证,若验证失败,就不会提交数据

var form = document.getElementById('form'),
warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
var self = this;
return function(){
if(beforeFn.apply(this, arguments) === false)
return;
return self.apply(this, arguments);
}
}//改进的AOP前置装饰函数
var validata = function(){
if(form.username.value === ''){
warn.textContent = '账号不能为空';
return false;
}
if(form.password.value === ''){
warn.textContent = '密码不能为空';
return false;
}
if(form.phonenum.value === ''){
warn.textContent = '手机号不能为空';
return false;
}
}
var submitMsg = function(){ //将提交的功能从validata函数中提取出来
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg);
return warn.textContent = '用户信息已成功提交至服务器';
}
submitMsg = submitMsg.before(validata);
//让表单验证函数成为表单提交函数的装饰者
form.submit.onclick = function(){
submitMsg();
};

策略模式重构

下面就该解决函数缺乏弹性的问题

使用策略模式就需要有策略对象/类和环境对象/类

毫无疑问策略对象中就应该装着校验规则

又考虑到页面可能不止有一个验证表单

最好写成工厂-类的形式

完整代码如下

var form = document.getElementById('form'),
warn = document.getElementById('warn');
Function.prototype.before = function(beforeFn){
var self = this;
return function(){
if(beforeFn.apply(this, arguments) === false)
return;
return self.apply(this, arguments);
}
}
var vldStrategy = { //策略对象-验证规则
isNonEmpty: function(value, warnMsg){ //输入不为空
if(value === '')
return warnMsg;
},
isLongEnough: function(value, length, warnMsg){ //输入足够长
if(value.length < length)
return warnMsg;
},
isShortEnough: function(value, length, warnMsg){ //输入足够短
if(value.length > length)
return warnMsg;
},
isMobile: function(value, warnMsg){ //手机号验证
var reg = /^1[3|5|8][0-9]{9}$/;
if(!reg.test(value))
return warnMsg;
}
}
var Validator = function(){ //环境类
this.rules = []; //数组用于存放负责验证的函数
};
Validator.prototype.add = function(domNode, ruleArr){ //添加验证规则
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
var tempArr = strategyArr.concat();
var ruleName = tempArr.shift();
tempArr.unshift(domNode.value);
tempArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, tempArr);
});
})(rule);
}
return this;
};
Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg){
warn.textContent = warnMsg;
return false;
}
}
}
var vld = new Validator();
vld.add(form.username, [
{
strategy: 'isNonEmpty',
warnMsg: '账号不能为空'
},
{
strategy: 'isLongEnough:4',
warnMsg: '账号不能小于4位'
},
{
strategy: 'isShortEnough:20',
warnMsg: '账号不能大于20位'
}
]).add(form.password, [
{
strategy: 'isNonEmpty',
warnMsg: '密码不能为空'
}
]).add(form.phonenum, [
{
strategy: 'isNonEmpty',
warnMsg: '手机号不能为空'
},
{
strategy: 'isMobile',
warnMsg: '手机号格式不正确'
}
]);
var submitMsg = function(){
var msg = {
username: form.username.value,
password: form.password.value,
phonenum: form.phonenum.value
}
//ajax('...', msg);
return warn.textContent = '用户信息已成功提交至服务器';
}
submitMsg = submitMsg.before(vld.start.bind(vld));
form.submit.onclick = function(){
submitMsg();
};
//这里只是模拟提交,实际应该用form.onsubmit








问题分析

总结一下易错的地方还有我敲得时候遇到的问题

Validator.prototype.add = function(domNode, ruleArr){
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
var tempArr = strategyArr.concat();
var ruleName = tempArr.shift();
tempArr.unshift(domNode.value);
tempArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, tempArr);
});
})(rule);
}
return this;
};

在Validator原型链上的add函数需要注意几个问题

首先添加IIFE立即执行函数解决闭包问题就不用多说了

函数内又嵌套了函数,导致了this被劫持,所以必须缓存this
var self = this;


最开始我没有拷贝这个数组而是直接使用的strategyArr

Validator.prototype.add = function(domNode, ruleArr){ //添加验证规则
var self = this;
for(var i = 0, rule; rule = ruleArr[i++];){
(function(rule){
var strategyArr = rule.strategy.split(':'),
warnMsg = rule.warnMsg;
self.rules.push(function(){
// var tempArr = strategyArr.concat();
var ruleName = strategyArr.shift();
strategyArr.unshift(domNode.value);
strategyArr.push(warnMsg);
return vldStrategy[ruleName].apply(domNode, strategyArr);
});
})(rule);
}
return this;
};

第一次提交没有问题,但再次提交就会报错



这是因为第一次提交后,闭包中的strategyArr已经改变

之后的提交,对这个数组进行操作就不是预期的结果了

在这个地方我犯了一个小错误,导致我断点调试了好长时间 __冏rz

Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg){
warn.textContent = warnMsg;
return false;
}
}
}

改正前的错误代码是这样的

Validator.prototype.start = function(){ //开始验证表单
for(var i = 0, vldFn; vldFn = this.rules[i++];){
var warnMsg = vldFn();
if(warnMsg)
warn.textContent = warnMsg;
return false;
}
}

没错,只是因为少加了那层大括号

可能是之前只有一行,后来添加
return false
的时候忘添加了

这里我只是为了简洁才不写大括号的

我们平时千万不要这么写代码,简直挖坑给自己跳

submitMsg = submitMsg.before(vld.start.bind(vld));

添加装饰者这个地方也要注意

如果不写bind就会发生this劫持,同样会报错
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: