Appearance
表单校验
在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑。
- 用户名不能为空。
- 密码长度不能少于 6 位。
- 手机号码必须符合格式。
html
<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>
const 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 函数的内部实现,这是违反开放—封闭原则的。第三是算法的复 用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的校验,那我们很可能 将这些校验逻辑复制得漫天遍野。
接下来使用策略模式重构表单验证代码,第一步显然是将校验逻辑封装成策略对象。
javascript
const strategies = {
isNonEmpty (value, errorMsg) {
if (value === '') {
return errorMsg
}
},
minLength (value, length, errorMsg) {
if (value.length < length) {
return errorMsg
}
},
isMobile (value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)){
return errorMsg
}
}
}第二步是实现 Validator 类,当用户提交表单后,通过 validateFunc 是否有确切返回 errorMsg,判断表单验证是否通过。validateFunc 中会创建 Validator 实例,可以通过 validator.add() 添加校验规则,添加完后会调用 validator.start()方法来启动校验。 如果 validator.start() 返回了一个确切的 errorMsg 字符串当作返回值,说明该次校验 没有通过,此时需要阻止表单的提交。
javascript
const Validator = function () {
this.cache = [] // 添加的校验规则
}
/**
* 添加校验规则
*
* @param {*} dom - 参与校验的 DOM
* @param {string} rule - 校验规则,是一个以冒号隔开的字符串。冒号前面的
* minLength 代表客户挑选的 strategy 对象,冒号后面的数字 6 表示在校验过程中所必需
* 的一些参数。'minLength:6' 的意思就是校验 registerForm.password 这个文本输入框
* 的 value 最小长度为 6。如果这个字符串中不包含冒号,说明校验过程中不需要额外的参数
* 信息,比如'isNonEmpty'。
* @param {string} errorMsg - 未通过校验时的错误提示
* @return {(string|undefined)} 未通过校验时返回 errorMsg,通过校验时返回 undefined
*/
Validator.prototype.add = function (dom, rule, errorMsg) {
const ary = rule.split(':') // 获取对应策略和校验过程中的额外参数
this.cache.push(function () {
const strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
Validator.prototype.start = function () {
const { cache } = this
for (let i = 0, validateFunc; validateFunc = cache[i++];) {
const errorMsg = validateFunc()
if (errorMsg) {
return errorMsg
}
}
}
const validateFunc = (function () {
const validator = new Validator()
// 添加校验规则
validator.add(registerForm.username, 'isNonEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:5', '密码长度至少为5')
// ...
return function () {
const errorMsg = validator.start()
return errorMsg
}
})()
submitBtn.addEventListener('click', () => {
const errorMsg = validateFunc()
if (errorMsg) {
// Fail, do something...
return
}
// Success, do something...
})给一个输入框同时添加多种校验规则。
javascript
validator.add(registerForm.username, [
{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
},
{
strategy: 'minLength: 6',
errorMsg: '用户名长度至少为6'
}
])
Validator.prototype.add = function (dom, rules, errorMsg) {
for (let i = 0, rule; rule = rules[i++];) {
const ary = rule.strategy.split(':')
const errorMsg = rule.errorMsg
this.cache.push(function () {
const strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
}