这几天在作一个用原生js写的项目,须要用到表单验证的功能。由于以前公司项目中的表单验证是写在业务里的,改起来特别的麻烦,就想本身写一个表单验证的小工具。原本想在网上找一个教程研究研究的,但没找到太好的,最后决定本身研究吧。文中示例的代码都是我本身写的demo,并无参考一些框架或者库的源码,因此代码可能比较难看, 重点仍是分析一下思路,后期我会把完善好的代码发到github上。
先说一下我本身写的表单验证工具的思路或者是原则吧:javascript
第一点没什么可说的,通常的工具都是这样作的,并且本身也吃了很多高耦合的亏,因此我仍是把它放出来了。我这个工具但愿达到的目的是,开发者只须要关注哪些数据须要验证,将数据传入给工具直接得到验证结果,让开发者更多的关注其余的业务。java
第二点是但愿开发者能够自定义验证规则,毕竟内置的规则再多,也架不住一个奇葩需求。git
第三点是验证规则和验证逻辑的分离,其实这一条更多的是为第二条服务的。github
这张流程图就是我这个工具验证逻辑,最开始判断的传入值是否能够为空,若是能够为空再去验证是否有其余的验证规则,若是没有就直接验证经过了(通常来讲,若是表单的值能够为空,大部分状况不会再存在其余规则了)。假如还有其余的验证规则,就去循环验证,一旦有一条规则没经过,就算验证失败。正则表达式
若是说输入的内容不能够为空,就去循环验证这些规则就行了,和上面同理,一旦有错就算验证失败。数组
class Vaildation{}复制代码
这就是验证工具的类,咱们先不着急往下写,先思考到底往类中传什么配置参数。框架
let vaild = [
{
value:this.username,
type: "用户名",
rules:[
"isDefine",
{
name:"limit",
check: true,
min:5,
max:12
}
]
},
{
value:this.password,
type: "密码",
rules:[
"isDefine",
{
name:"mix",
check:true,
},
{
name:"limit",
check: true,
min:5,
max:12
}
]
}
];
复制代码
这是我想传到类中的配置(若是你本身想设计一个的话也能够用别的样式,数据、对象均可以),它是一个数组,里面包含了每个须要验证的对象,简单介绍一下配置中的属性:工具
value: 须要认证的数据的值,类型是string或者number
type: 传入这个数据的名称或者标签,好比用户名、密码、邮箱或者电话等,类型是字符串或者数组
rules: 须要使用哪些验证规则,类型是array。
rules有两种写法,一种是简写好比:['isDefine', 'limit'],意思是不能为空,且有字数限制,使用默认
的规则;
也能够自定义具体的规则好比:['isDefine', {name: limit, check:true, min:5, max:12}], check参数
的意思是使规则生效,若是你写成false即便你写上这个规则也不会生效,min和max就好理解了,就是限制具体的
字数,默认的是6-16个字符。复制代码
让咱们接着回到类的设计中:ui
class Vaildation{
constructor(){
this.isCheck = false;
this.vaild_item = [];
this.vaild;
}
}
复制代码
isCheck: 验证是否经过,类型是布尔值
vaild_item: 存放咱们传入类中的参数
vaild: 验证成功或失败时返回的消息复制代码
接下来,让咱们来设计验证规则rules:this
class Vaildation{
constructor(){
......
}
rules(){
return {
mix: (vaild) => {
for(let i = 0; i < vaild.rules.length; i++){
if(vaild.rules[i] == "mix" || vaild.rules[i].name == "mix"){
if(vaild.rules[i].check){
if(!vaild.rules[i].newRules){
return new RegExp(/^[a-zA-Z0-9]*([a-zA-Z][0-9]|[0-9][a-zA-Z])[a-zA-Z0-9]*$/).test(vaild.value);
}else{
return new RegExp(vaild.rules[i].newRules).test(vaild.value);
}
}else{
return true;
}
}
}
},
limit: (vaild) => {
let min;
let max;
vaild.rules.forEach((item) => {
if(item == "limit" || item.name == "limit"){
min = item.min || 6;
max = item.max || 16;
}
})
if(vaild.value.length < min || vaild.value.length > max){
return false;
}else{
return true;
}
},
isDefine: (vaild) => {
return !!vaild.value.trim();
},
}
}
}
复制代码
rules方法中返回了一个规则对象,我这里内置了两个规则:
一个是"mix",规定输入的内容是数字和字母的混合;
另外一个"limit",规定输入内容字符数量的限制;
它们的参数是以前配置数组中的rules属性的值,即["isDefine", "mix", "limit"]
,咱们能够看到这些规则是支持传入字符串或者是对象的,传入对象时还能够作一些额外的配置,好比当咱们有一些奇葩需求好比“输入的内容每隔3个字必须有一个字母(for god's sake,天杀的需求)”,咱们能够在"mix"中加入新的正则表达式:
{
name:"mix",
check:true,
newRules: 新的正则表达式,
},复制代码
固然,若是你有新的规则好比电话,邮箱之类的,你也能够在新建Vaildation
实例后手动添加进去,方便自定义:
let vaildation = new Vaildation();
vaild.rules()[newRules] = function(vaild){
//new Rules
}复制代码
有了规则,咱们还须要错误提示,我这里每一个规则的提示都使用了和规则同样的名称,方便处理验证逻辑时使用:
error(){
return {
mix: (vaild)=>{
return vaild.type + "必须为字母和数字组合";
},
limit: (vaild) => {
let min;
let max;
vaild.rules.forEach((item) => {
if(item == "limit" || item.name == "limit"){
min = item.min || 6;
max = item.max || 16;
}
})
return vaild.type + "位数为" + min + "-" + max + "位";
},
define: (vaild) => {
return vaild.type + "不能为空";
}
}
}复制代码
处理错误的时候咱们就用到配置中的type属性了,固然也能够直接在error中写死错误处理的文案,这些都无所谓拉。
最后咱们要看的是验证的逻辑处理:
check_result(check, err = null){
return {
check: check, //验证是否为空
err: err, //验证失败时的文案
}
}
check(){
for(let i = 0; i < this.vaild_item.length; i++){
//循环每个配置
let vaild = this.vaild_item[i].rules.findIndex(function(val){
//查找是否有isDefine规则
return val === "isDefine";
});
if(vaild > -1){
let rules_arr = this.vaild_item[i].rules;
//若是存在isDefine规则,就把它去除,去处理剩下的规则
rules_arr.splice(vaild,1);
if(rules_arr.length > 0){
for(let j = 0; j < rules_arr.length; j++){
for(let o in this.rules()){
if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this,this.vaild_item[i])){
//将传入的配置规则和类中自带的规则进行匹配,若是匹配上而且匹配失败,返回验证信息
this.vaild = [this.check_result(false,this.error()[rules_arr[j]['name']].call(this,this.vaild_item[i]))];
this.isCheck = false;
return this.vaild;
}else{
this.isCheck = true;
this.vaild = [this.check_result(true)];
}
}
}
}
}else{
if(this.vaild_item[i].rules.length > 0 && !!this.vaild_item[i].value){
let rules_arr = this.vaild_item[i].rules;
for(let j = 0; j < rules_arr.length; j++){
//这部分处理逻辑和上面相同
for(let o in this.rules()){
if(rules_arr[j]['name'] == o && !this.rules()[rules_arr[j]['name']].call(this,this.vaild_item[i])){
this.vaild = [this.check_result(false,this.error()[rules_arr[j]['name']].call(this,this.vaild_item[i]))];
this.isCheck = false;
return this.vaild;
}else{
this.isCheck = true;
this.vaild = [this.check_result(true)];
}
}
}
}
}
}
return this.vaild;
}
复制代码
check方法是核心逻辑,实际上就是上面流程图的内容,一些重要的地方我也备注了(实在抱歉,这部分代码写的有点儿丑,demo...demo...)。
最后我来展现一下这个工具用起来大概是什么样子的:
//配置文件
let vaild = [
{
value:this.username,
type: "用户名",
rules:[
"isDefine",
{
name:"limit",
check: true,
min:5,
max:12
}
]
},
{
value:this.password,
type: "密码",
rules:[
"isDefine",
{
name:"mix",
check:true,
},
{
name:"limit",
check: true,
min:5,
max:12
}
]
}
];
let vaildation = new Vaildation(); //新建一个Vaildation实例
vaildation.check_items = vaild; //将配置传给实例中的check_items
let result = vaildation.check(); //ok,你已经拿到结果了
//噢,别忘了能够在result.check或者vaildation.isCheck中拿到是否成功的结果复制代码
这篇文章仅仅是我设计这个工具时的一个思路,可能还存在一堆问题,就先抛砖引玉一下,也但愿你们能多多指正,最后感谢你们的收看。