(e) {
e.preventDefault();
fomrSubmit();
这里我们定义了before方法,也同样能实现之前的功能。before方法定义的与否只是看自己的习惯而已,我这里还是采用原型上定义方法在继续下面的讲解。
看到这里,聪明的你肯定会说,我点击提交按钮的时候,分别去执行这两个函数不就行了嘛?对啊,我们搞这么半天不就是为了实现这个功能嘛,只是代码写的不一样。
我相信更聪明的你想到了,我们这个实现方式就是AOP,也是装饰者模式在JavaScript中的一种体现。或许现在你觉得这个不重要,但你维护别人的代码或者使用第三方库的时候,在不修改别人源代码的时候(别人的代码或许是混合压缩过的),增强原函数的功能,你就知道这个模式有什么用处了。好了这部份的优化我们就到这里。
接下来我们看一下,关于校验的问题,就像我之前所说的那样如果校验字段很少或者要校验的东西很少(比如用户名判断是否为空,不判断长度)你写if else没有错,但是事与愿违,一般涉及到表单校验是要校验很多的东西。如果你还是if else嵌套下去,我相信你自己都看不下去自己写的代码。
我们先来看一段代码:
<script>
var submit = document.getElementById('submit');
var username = document.getElementsByClassName('username')[0];
var password = document.getElementsByClassName('password')[0];
var usernameErrText = document.getElementsByClassName('username-err')[0];
var passworErrText = document.getElementsByClassName('password-err')[0];
// 点击提交按钮,发送数据(前提是校验通过)
// 校验规则,用户名和密码不为空即可
Function.prototype.before = function (fn) {
var _that = this; // 保存原函数的引用
return function () {
fn.apply(this, arguments) === 0 && _that.apply(this, arguments)
var postData = function () {
console.log('发送数据给后台');
// 定义表单校验对象
var validataor = (function () {
// 定义校验规则
var rules = {
isNotEmpty: function (dom, errMsg) {
if (!dom.value.trim()) {
return errMsg;
// 需要校验的函数集合
var caches = [];
// 错误数量
var errNum = 0;
return {
start: function () {
for (var i = 0, func; func = caches[i++];) {
if (func()) {
errNum++;
add: function (dom, rule, errMsg, errShowDom) {
caches.push(function () {
var msg = rules[rule](dom, errMsg);
msg && (errShowDom.innerHTML = msg) || (errShowDom.innerHTML = '');
return msg;
isCheckAll:
function () {
var num = errNum;
errNum = 0;
caches.length = 0;
return num;
})();
var fomrSubmit = postData.before(function () {
validataor.add(username, 'isNotEmpty', '用户名必填', usernameErrText);
validataor.add(password, 'isNotEmpty', '密码必填', passworErrText);
validataor.start();
return validataor.isCheckAll()
submit.onclick = function (e) {
e.preventDefault();
fomrSubmit();
</script>
这段代码,我们也实现了验证功能,咋一看我擦,怎么这么复杂。没错封装代码就是有可能带来额外的代码,但是你看一下我们还有if else那种难看的嵌套嘛。
这里我们用validataor对象,实现校验规则的新增,检测。
而且这样子写我们还可以随意的配置自己的校验规则:
// 定义表单校验对象
var validataor = (function (rules) {
// 需要校验的函数集合
var caches = [];
// 错误数量
var errNum = 0;
return {
start: function () {
for (var i = 0, func; func = caches[i++];) {
if (func()) {
errNum++;
add: function (dom, rule, errMsg, errShowDom) {
caches.push(function () {
var msg = rules[rule](dom, errMsg);
msg && (errShowDom.innerHTML = msg) || (errShowDom.innerHTML = '');
return msg;
isCheckAll: function () {
var num = errNum;
errNum = 0;
caches.length = 0;
return num;
isNotEmpty: function (dom, errMsg) {
if (!dom.value.trim()) {
return errMsg;
isPhone: function(){
// 校验是否是手机号码
看现在我们可以随意配置,你只要知道这个validataor的用法,岂不是很简单,不需要在一个一个if else的去判断,多优雅。而且我们这个validataor对象很方便移植。
聪明的你应该看出了隐藏在代码中的策略模式的使用,这里我就不指出,免得班门弄斧了。
好了改造我们现在差不多了,我们现在需要升级。实际中不可能每一个字段都只有一种校验,有的有着多个校验。我们拿密码距离,密码不能为空而且长度不能小于6位。
现在我们有一个最坏原则,那就是表单校验的其中一个字段有多个校验,我们假设它在校验的时候遇到第一个校验不通过的情景,就停止后面的校验(本来也是这样子),
那么什么是最坏原则了,那就是我们所期望校验的时候,总会存在校验不通过的情况,如果连最坏原则都通过了,那就说明你的所有校验都通过。
我们先来看一段代码:
var Chain = function (fn) {
this.fn = fn;
this.nextChain = null;
Chain.prototype.setNextChain = function (nextChain) {
this.nextChain = nextChain;
Chain.prototype.next = function () {
var ret = this.fn.apply(this, arguments);
if (ret === 'next') {
return this.nextChain && this.nextChain.next.apply(this.nextChain, arguments);
return ret;
var fn1 = function (value) {
if (value < 10 && value > 5) {
console.log('fn1满足');
else {
return 'next';
var fn2 = function (value) {
console.log(value);
if (value > 10 && value < 20) {
console.log('fn2满足');
else {
return 'next';
var fn3 = function (value) {
if (value > 30) {
console.log('fn3满足');
else {
return 'next';
var chainF1 = new Chain(fn1);
var chainF2 = new Chain(fn2);
var chainF3 = new Chain(fn3);
chainF1.setNextChain(chainF2);
chainF2.setNextChain(chainF3);
chainF1.next(8);
这段代码,我们测试所给数字的大小范围,通过Chain类的各个实例,我们完全摒弃了以前的if else的嵌套,是不是很优雅。每一个执行函数,如果满足它的要求,就会停止所有的程序执行,如果不满足,那么就把执行权交给下一个chain实例中的执行函数。如果最后的结果返回的不是next那么就代表所有的校验都通过了。
我们现在在优化一下这段代码:
var fn1 = function (value) {
if (value < 10 && value > 5) {
console.log('fn1满足');
else {
return 'next';
var fn2 = function (value) {
if (value > 10 && value < 20) {
console.log('fn2满足');
else {
return 'next';
var fn3 = function (value) {
if (value > 30) {
console.log('fn3满足');
else {
return 'next';
Function.prototype.after = function (fn) {
var _that = this;
return function () {
var ret = _that.apply(this, arguments);
if (ret === 'next') {
return fn.apply(this, arguments);
return ret;
var start = fn1.after(fn2).after(fn3);
start(18);
现在你来来看看这个威力,是不是很强大,聪明的你肯定知道这个after是AOP的实现。
好了,现在有了这段代码我们,就来实现我们的完整校验。
1.支持多字段校验
2.一个字段支持多种校验
3.校验一个出错,停止后面所有的校验
现在我就直接给出完整的代码,我相信你一定能看懂的(不是相信,是你一定能,因为你很棒啊):
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>优雅的表单校验</title>
<style>
margin: 0;
padding: 0;
.myForm {
width: 300px;
padding-bottom: 30px;
border: 1px solid red;
margin: 100px auto;
.form-component>div:first-child {
margin: 10px 20px 5px;
border: 1px solid #ccc;
border-radius: 5px
;
overflow: hidden;
input {
outline: none;
display: block;
border: none;
padding: 10px;
#submit {
padding: 5px 20px;
outline: none;
border: 1px solid skyblue;
background-color: skyblue;
letter-spacing: 1em;
border-radius: 5px;
margin: 0 auto;
display: block;
.errMsg {
color: red;
margin: 0 20px;
</style>
</head>
<div id="root">
<form class="myForm">
<div class="form-component">
<div><input type="text" class="username" placeholder="请输入用户名"></div>
<p class="username-err errMsg"></p>
</div>
</div>
<div class="form-component">
<div><input type="password" class="password" placeholder="请输入密码"></div>
<p class="password-err errMsg"></p>
</div>
</div>
<button id="submit">提交</button>
</form>
</div>
<script>
var submit = document.getElementById('submit');
var username = document.getElementsByClassName('username')[0];
var password = document.getElementsByClassName('password')[0];
var usernameErrText = document.getElementsByClassName('username-err')[0];
var passworErrText = document.getElementsByClassName('password-err')[0];
// 点击提交按钮,发送数据(前提是校验通过)
// 校验规则,用户名和密码不为空即可
Function.prototype.before = function (fn) {
var _that = this; // 保存原函数的引用
return function () {
fn.apply(this, arguments) === 0 && _that.apply(this, arguments)
Function.prototype.after = function (fn) {
var _that
= this;
return function () {
var ret = _that.apply(this, arguments);
// 最坏原则,这次校验通过,假设后面有校验不会通过
if (typeof ret === 'undefined') {
return fn.apply(this, arguments);
// 如果这次校验不通过,那么停止校验,返回错误信息
return ret;
var validataor = (function (validataRules) {
var caches = [];
var errNum = 0;
return {
add: function (dom, rules, errShowDom) {
var fnsArr = [];
for (var i = 0, ruleObj; ruleObj = rules[i++];) {
var ruleArr = ruleObj.rule.split(':');
var rule = ruleArr.shift();
ruleArr.unshift(dom);
ruleArr.push(ruleObj.errMsg);
fnsArr.push(validataRules[rule].bind(dom, ...ruleArr));
if (fnsArr.length) {
var fn = fnsArr.shift();
while (fnsArr.length) {
fn = fn.after(fnsArr.shift());
caches.push({
fn: fn,
container: errShowDom
start: function () {
for (var i = 0, cacheObj; cacheObj = caches[i++];) {
var msg = cacheObj.fn();
cacheObj.container.innerHTML = msg || '';
imsg && ++errNum;
caches = [];
var num = errNum;
errNum = 0;
return num;
isNotEmpty: function (dom, errMsg) {
if (!dom.value) {
return errMsg;
isPhone: function () {
// 校验是否是手机号码
minlength: function (dom, length, errMsg) {
if (dom.value.length < length) {
return errMsg;
var postData = function () {
console.log('发送数据给后台');
var fomrSubmit = postData.before(function () {
validataor.add(username, [
{ rule: 'isNotEmpty', errMsg: '用户名必填' }
], usernameErrText);
validataor.add(password, [
{ rule: 'isNotEmpty', errMsg: '密码必填' },
{ rule: 'minlength:10', errMsg: '密码长度必须大于等于10位' },
], passworErrText);
return validataor.start();
submit.onclick = function (e) {
e.preventDefault();
fomrSubmit();
</script>
</body>
</html>
现在我们只需要自己配置好校验规则,就可以实现不同字段的校验,当然本案例代码肯定只有优化的地方,我现在是写最多的代码,希望理解的够清楚一些。
相比之前的if else现在我个人感觉是好多了。但是我们发现了代码量增多了,多的就是validatator这一段代码,这段代码其实不难。牛逼的你应该能从本案例中发现许多设计模式的运用(策略模式,职责链模式,装饰者模式),还有AOP的风格的编程,你看见确实是增加了代码量,所以实际项目还是要看看引入设计模式会不会得不偿失。
反正在这个例子中,我认为是没有错的,只需要自己配置就行,代码中不变的地方已经被我们封装起来了,变化的地方我们提出来了,其实这就是设计模式通用的手段,还记得before函数嘛,其实这个函数也是设计模式的一种原则开放-封闭(在不修改源码的情况下,增加原函数的功能)。
好了,到这儿就再见了。