优雅的表单验证模式--策略设计模式和ES6的Proxy代理模式

来源:互联网 发布:java synchronized原理 编辑:程序博客网 时间:2024/06/08 18:34

转载自 作者 @jawil 原文,原文有删改


网站的交互,离不开表单的提交,而一个健壮的表单离不开对表单内容的校验。
假设我们正在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑。

  1. 所有选项不能为空
  2. 用户名长度不能少于6位
  3. 密码长度不能少于6位
  4. 手机号码必须符合格式
  5. 邮箱地址必须符合格式

一般情况下,我们都会选择一种较为传统的方式,对表单内容进行校验,假设表单结构如下:

<form action="http://xxx.com/register" id="registerForm" method="post">        <div class="form-group">            <label for="user">请输入用户名:</label>            <input type="text" class="form-control" id="user" name="userName">        </div>        <div class="form-group">            <label for="pwd">请输入密码:</label>            <input type="password" class="form-control" id="pwd" name="passWord">        </div>        <div class="form-group">            <label for="phone">请输入手机号码:</label>            <input type="tel" class="form-control" id="phone" name="phoneNumber">        </div>        <div class="form-group">            <label for="email">请输入邮箱:</label>            <input type="text" class="form-control" id="email" name="emailAddress">        </div>        <button type="button" class="btn btn-default">Submit</button>    </form>

传统模式

对应的 JavaScript 校验规则如下:

// registerForm为所要提交表单的 idlet registerForm = document.querySelector('#registerForm')registerForm.addEventListener('submit', function() {    if (registerForm.userName.value === '') {        alert('用户名不能为空!')        return false    }    if (registerForm.userName.length < 6) {        alert('用户名长度不能少于6位!')        return false    }    if (registerForm.passWord.value === '') {        alert('密码不能为空!')        return false    }    if (registerForm.passWord.value.length < 6) {        alert('密码长度不能少于6位!')        return false    }    if (registerForm.phoneNumber.value === '') {        alert('手机号码不能为空!')        return false    }    if (!/^1(3|5|7|8|9)[0-9]{9}$/.test(registerForm.phoneNumber.value)) {        alert('手机号码格式不正确!')        return false    }    if (registerForm.emailAddress.value === '') {        alert('邮箱地址不能为空!')        return false    }    if (!/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*    $/.test(registerForm.emailAddress.value)) {        alert('邮箱地址格式不正确!')        return false    }}, false)

这样编写代码,的确能够完成业务的需求,能够完成表单的验证,但是存在很多问题,比如:

  • registerForm.addEventListener绑定的函数比较庞大,包含了很多的if-else语句,看着都恶心,这些语句需要覆盖所有的校验规则。

  • registerForm.addEventListener绑定的函数缺乏弹性,如果增加了一种新的校验规则,或者想要把密码的长度校验从6改成8,我们都必须深入registerForm.addEventListener绑定的函数的内部实现,这是违反了开放-封闭原则的。

  • 算法的复用性差,如果程序中增加了另一个表单,这个表单也需要进行一些类似的校验,那我们很可能将这些校验逻辑复制得漫天遍野。

策略模式

策略模式的组成:

  1. 抽象策略角色:策略类,通常由一个接口或者抽象类实现。
  2. 具体策略角色:包装了相关的算法和行为。
  3. 环境角色:持有一个策略类的引用,最终给客户端用的。
  • 具体策略角色——编写策略类
const strategies = {      isNonEmpty(value, errorMsg) {          return value === '' ?              errorMsg : void 0      },      minLength(value, length, errorMsg) {          return value.length < length ?              errorMsg : void 0      },      isMoblie(value, errorMsg) {          return !/^1(3|5|7|8|9)[0-9]{9}$/.test(value) ?              errorMsg : void 0      },      isEmail(value, errorMsg) {          return !/^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value) ?              errorMsg : void 0      }  }
  • 抽象策略角色——编写Validator类
class Validator {    constructor() {        this.cache = [] //保存校验规则    }    add(dom, rules) {        for (let rule of rules) {            let strategyAry = rule.strategy.split(':') //例如['minLength',6]            let errorMsg = rule.errorMsg //'用户名不能为空'            this.cache.push(() => {                let strategy = strategyAry.shift() //用户挑选的strategy                strategyAry.unshift(dom.value) //把input的value添加进参数列表                strategyAry.push(errorMsg) //把errorMsg添加进参数列表,[dom.value,6,errorMsg]                return strategies[strategy].apply(dom, strategyAry)            })        }    }    start() {        for (let validatorFunc of this.cache) {            let errorMsg = validatorFunc()//开始校验,并取得校验后的返回信息            if (errorMsg) {//r如果有确切返回值,说明校验没有通过                return errorMsg            }        }    }}
  • 环境角色——客户端调用代码
let registerForm = document.querySelector('#registerForm')const validatorFunc = () => {    let validator = new Validator()    validator.add(registerForm.userName, [{        strategy: 'isNonEmpty',        errorMsg: '用户名不能为空!'    }, {        strategy: 'minLength:6',        errorMsg: '用户名长度不能小于6位!'    }])    validator.add(registerForm.passWord, [{        strategy: 'isNonEmpty',        errorMsg: '密码不能为空!'    }, {        strategy: 'minLength:',        errorMsg: '密码长度不能小于6位!'    }])    validator.add(registerForm.phoneNumber, [{        strategy: 'isNonEmpty',        errorMsg: '手机号码不能为空!'    }, {        strategy: 'isMoblie',        errorMsg: '手机号码格式不正确!'    }])    validator.add(registerForm.emailAddress, [{        strategy: 'isNonEmpty',        errorMsg: '邮箱地址不能为空!'    }, {        strategy: 'isEmail',        errorMsg: '邮箱地址格式不正确!'    }])    let errorMsg = validator.start()    return errorMsg}registerForm.addEventListener('submit', function(e) {    let errorMsg = validatorFunc()    if (errorMsg) {        // 注意,这里阻止表单提交,应该是通过阻止默认事件的方式,        // 而 `return false;` 或者 `return;` 都是没什么卵用的        e.preventDefault()        alert(errorMsg)    }})

Proxy代理模式

  • 利用proxy拦截不符合要求的数据:
let validator = (target, validator, errorMsg)=> {    return new Proxy(target, {      _validator: validator,      set(target, key, value, proxy) {        if(value === '') {          alert(`${errorMsg[key]}不能为空!`)          return target[key] = false        }        let va = this._validator[key]        if(!!va(value)) {          return Reflect.set(target, key, value, proxy)        } else {          alert(`${errorMsg[key]}格式不正确`)          return target[key] = false        }      }    })  }
  • 负责校验的逻辑代码:
const validators = {    name(value) {      return value.length > 6    },    password(value) {      return value.length > 6    },    mobile(value) {      return /^1(3|5|7|8|9)[0-9]{9}$/.test(value)    },    email(value) {      return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(value)    }  }
  • 客户端调用代码:
const errorMsg = { name: '用户名', password: '密码', mobile: '手机号码', email: '邮箱地址' }  const vali = validator({}, validators, errorMsg)  let registerForm = document.querySelector('#registerForm')  registerForm.addEventListener('submit', (e)=>{    let validatorNext = function* (){      yield vali.name = registerForm.userName.value      yield vali.password = registerForm.passWord.value      yield vali.mobile = registerForm.phoneNumber.value      yield vali.email = registerForm.emailAddress.value    }    let validator = validatorNext()    validator.next()    let s = vali.name && validator.next() //上一步的校验通过才执行下一步    s = s ? vali.password && validator.next() : s    s = s ? vali.mobile && validator.next() : s    s = s ? vali.email && validator.next() : s    // 如果验证不通过,阻止表单提交    !s && e.preventDefault()  })