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

Posted 刻刻帝丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用策略模式与装饰模式扩展JavaScript表单验证功能相关的知识,希望对你有一定的参考价值。

tip:有问题或者需要大厂内推的+我脉脉哦:丛培森 ٩( ‘ω’ )و

【本文源址:http://blog.csdn.net/q1056843325/article/details/55097262 转载请添加该地址】

昨晚为了练习一下这两个设计模式
就写了一个表单验证的小例子
敲得过程中还是遇到一些问题的
下面就给大家分享一下
#简单的表单验证
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劫持,同样会报错

主页传送门

以上是关于利用策略模式与装饰模式扩展JavaScript表单验证功能的主要内容,如果未能解决你的问题,请参考以下文章

对设计模式的总结之装饰模式与代理模式

设计模式——装饰者/策略/观察者模式

设计模式回顾:策略模式代理模式装饰者模式的区别

JavaBIO利用装饰器模式来组织和扩展接口

设计模式_装饰器模式

设计模式