javascript装饰者模式和策略模式实现可复用表单验证

最近在看《设计模式和开发实践》这本书,记录一下;

html 部分
        <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>

我们先来看一下未用装饰者模式和策略模式实现的表单验证:

var form = document.getElementById('form'),
    warn = document.getElementById('warn');
var formSubmit = 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 () {
    formSubmit();
}

我们先来看一下上面的 formSubmit 方法:
formSubmit 在此承担了两个职责:

  • 验证用户输入合法性;
  • 提交ajax请求;
    这种写法一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。

我们先把 formSubmit验证用户输入 和 提交ajax 请求的代码,进行分离,把验证用户输入的逻辑放到 validata 函数中,并约定当 validata 验证未通过的时候,返回 false ,表示校验未通过,代码如下:

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 formSubmit = function() {
  if (validata() === false) {
    return;
  }
  var msg = {
       username: form.username.value,
       password: form.password.value,
       phonenum: form.phonenum.value
   }
   //ajax('...', msg); ajax提交数据略
   return warn.textContent = '用户信息已成功提交至服务器';
}
form.submit.onclick = function () {
    formSubmit();
}

现在的代码已经有了一些改进,我们把校验逻辑放到了 validata 函数中,但 formSubmit 函数内部还要计算 validata 函数的返回值,并没有完全分离开来;
接下来我们用 装饰者模式 优化上面的这段代码,使 validataformSubmit 完全分离开来,如果 beforeFn 的执行结果返回 false,表示不再执行后面的原函数;
那么问题来了,什么是装饰者模式呢?

装饰者模式定义:

在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责,这种给对象动态地增加职责的方式称为 装饰者(decorator)模式
代码如下:

Function.prototype.before = function (beforeFn) {
    var self = this;
    return function () {
        if (beforeFn.apply(this, arguments) === false) {
            return;
        }
        return self.apply(this, arguments);
    }
}

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 formSubmit = function() {
  var msg = {
       username: form.username.value,
       password: form.password.value,
       phonenum: form.phonenum.value
   }
   //ajax('...', msg); ajax提交数据略
   return warn.textContent = '用户信息已成功提交至服务器';
}
formSubmit = formSubmit.before(validata);
form.submit.onclick = function () {
    formSubmit();
}

在上面的这段代码中,校验输入和提交表单的代码完全分离出来,他们不再有任何耦合关系,formSubmit = formSubmit.before(validata)这句代码如同把校验规则动态接在 formSubmit函数之前,validata成为一个即插即用的函数,它甚至可以被写成配置文件的形式,有利于我们分开维护这两个函数,再利用策略模式稍加改造,就可以把这些校验规则都写成插件的形式,用在不同的项目中。

装饰者模式 + 策略模式 实现表单验证

最终代码如下:

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 formSubmit = function () {
    var msg = {
        username: form.username.value,
        password: form.password.value,
        phonenum: form.phonenum.value
    }
    //ajax('...', msg);
    return warn.textContent = '用户信息已成功提交至服务器';
}
formSubmit = formSubmit.before(vld.start.bind(vld));
form.submit.onclick = function () {
    formSubmit();
};

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352