JS常见的API扩展形式(prototype、jquery、vue插件封装)以及怎样设计出易扩展的表单验证功能?
常见的API扩展形式
prototype
比如我现在有一个需求,给定一个字符串,给方法传递一个参数为数字类型来确定当前字符串重复次数,例如:
'abc'.repeatStringNumTimes(3) // abcabcabc
如果按照一般的思维就是我们把这个方法绑定到String的原型上,如下代码:
String.prototype.repeatStringNumTimes = String.prototype.repeatStringNumTimes || function(times) { var str = ''; for(var i = 0; i < times; i++) { str += this; } return str; }
jQuery
根据《jQuery高级编程》的描述,jQuery插件开发方式主要有三种:
-
通过$.extend()来扩展jQuery
-
通过$.fn 向jQuery添加新的方法
-
通过$.widget()应用jQuery UI的部件工厂方式创建
通常我们使用第二种方法来进行简单插件开发,说简单是相对于第三种方式。第三种方式是用来开发更高级jQuery部件的,该模式开发出来的部件带有很多jQuery内建的特性,比如插件的状态信息自动保存,各种关于插件的常用方法等,非常贴心,这里不细说。
而第一种方式又太简单,仅仅是在jQuery命名空间或者理解成jQuery身上添加了一个静态方法而以。所以我们调用通过.extend()添加的函数时直接通过.extend()添加的函数时直接通过符号调用($.myfunction())而不需要选中DOM元素($('#example').myfunction())。请看下面的例子。
$.extend({ sayHello: function(name) { console.log('Hello,' + (name ? name : 'Dude') + '!'); } }) $.sayHello(); //调用 $.sayHello('Wayou'); //带参调用
看一个jquery封装的面向对象的插件开发代码:
//定义Beautifier的构造函数 var Beautifier = function(ele, opt) { this.$element = ele, this.defaults = { 'color': 'red', 'fontSize': '12px', 'textDecoration':'none' }, this.options = $.extend({}, this.defaults, opt) } //定义Beautifier的方法 Beautifier.prototype = { beautify: function() { return this.$element.css({ 'color': this.options.color, 'fontSize': this.options.fontSize, 'textDecoration': this.options.textDecoration }); } } //在插件中使用Beautifier对象 $.fn.myPlugin = function(options) { //创建Beautifier的实体 var beautifier = new Beautifier(this, options); //调用其方法 return beautifier.beautify(); }
调用方式:
$(function() { $('a').myPlugin({ 'color': '#2C9929', 'fontSize': '20px' }); })
感兴趣的可以详细查看文章:《jQuery插件开发进阶》
vue
插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:
- 1.添加全局方法或者属性,如: vue-custom-element
- 2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
- 3.通过全局 mixin 方法添加一些组件选项,如: vue-router
- 4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
Vue.js 的插件应当有一个公开方法 install
。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或属性 Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } } export default MyPlugin
封装的插件怎样使用?通过全局方法 Vue.use()
使用插件:
// 调用 `MyPlugin.install(Vue)` Vue.use(MyPlugin)
也可以传入一个选项对象:
Vue.use(MyPlugin, { someOption: true })
实现一个表单验证
设计表单验证的规则就是开放-封闭验证,使用策略模式封装。
一般方案
我们先写一个html代码片段好验证代码:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> <p> <label>请输入用户名:</label> <input type="text" name="userName" /> </p> <p> <label>请输入密码:</label> <input type="text" name="password" /> </p> <p> <label>请输入手机号码:</label> <input type="text" name="phoneNumber" /> </p> <div> <button type="submit">提交</button> </div> </form>
在form上绑定的submit,想实现的submitValidate方法代码如下:
function submitValidate() { var registerForm = document.getElementById("registerForm"); var rulesArr = [ { el: registerForm.userName.value, rules: [{rule: 'isNonEmpty',message: '用户名不能为空'},{rule:'minLength:3',message: '用户名长度不能小于3位'}] }, { el: registerForm.password.value, rules: [{rule: 'isNonEmpty',message: '密码不能为空'},{rule:'minLength:6',message: '密码的长度不能小于6位'}] }, { el: registerForm.phoneNumber.value, rules: [{rule: 'isNonEmpty',message: '手机号不能为空'},{rule:'isMobile',message: '手机号码格式不正确'}] } ] var resultMsg = validate.check(rulesArr); if(resultMsg) { alert(resultMsg); return false; } return true; }
下面我们编写validate验证方法,代码如下:
var validate = (function() { // 校验规则的各种算法 var rules = { // 判断非空 isNonEmpty: function(value,errorMsg) { if(!value) { return errorMsg; } }, // 判断最小长度 minLength: function(value,length,errorMsg) { if(value.toString().length < length) { return errorMsg; } }, // 判断最大长度 maxLength: function(value,length,errorMsg) { if(value.toString().length > length) { return errorMsg; } }, // 判断手机号 isMobile: function(value,errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判断座机电话 isTel: function(value,errorMsg) { if(!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校验方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for(var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if(arr[i].el === undefined) { return '没有当前字段' } for(var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null,checkRule); if(ruleMsg) { return ruleMsg; } } } } } })();
以上代码就是常规实现的表单验证,看似也够用了,但是如果一个系统中有多个表单提交验证,并且有部分验证方式不是那么通用,我们不可能再去修改代码中的rules,那样这个rules校验的算法越来越多,并且很多不是很通用的,那么我们怎样来解决呢?
在验证函数中增加一个添加规则算法的方法,代码如下:
var validate = (function() { // 校验规则的各种算法 var rules = { // 判断非空 isNonEmpty: function(value,errorMsg) { if(!value) { return errorMsg; } }, // 判断最小长度 minLength: function(value,length,errorMsg) { if(value.toString().length < length) { return errorMsg; } }, // 判断最大长度 maxLength: function(value,length,errorMsg) { if(value.toString().length > length) { return errorMsg; } }, // 判断手机号 isMobile: function(value,errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判断座机电话 isTel: function(value,errorMsg) { if(!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校验方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for(var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if(arr[i].el === undefined) { return '没有当前字段' } for(var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null,checkRule); if(ruleMsg) { return ruleMsg; } } } }, // 添加规则 addRule: function(ruleName,fn) { rules[ruleName] = fn; } } })();
比如用户名只能是字母跟数字的组合,那么我们就添加一个规则,代码如下:
validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } })
submitValidate方法的代码修改为如下:
function submitValidate() { var registerForm = document.getElementById("registerForm"); validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rulesArr = [{ el: registerForm.userName.value, rules: [{ rule: 'isNonEmpty', message: '用户名不能为空' }, { rule: 'minLength:3', message: '用户名长度不能小于3位' }, { rule: 'isAlphaNum', message: '用户名只能是数字跟字母的组合' }] }, { el: registerForm.password.value, rules: [{ rule: 'isNonEmpty', message: '密码不能为空' }, { rule: 'minLength:6', message: '密码的长度不能小于6位' }] }, { el: registerForm.phoneNumber.value, rules: [{ rule: 'isNonEmpty', message: '手机号不能为空' }, { rule: 'isMobile', message: '手机号码格式不正确' }] } ] var resultMsg = validate.check(rulesArr); if (resultMsg) { alert(resultMsg); return false; } return true; }
运行效果如图所示:
升级方案
如果我们想实现如图这样的验证结果:
那么我们就需要保存所有元素的错误信息,那么我们新添加一个checkAll的方法,代码如下:
var validate = (function() { // 校验规则的各种算法 var rules = { // 判断非空 isNonEmpty: function(value, errorMsg) { if (!value) { return errorMsg; } }, // 判断最小长度 minLength: function(value, length, errorMsg) { if (value.toString().length < length) { return errorMsg; } }, // 判断最大长度 maxLength: function(value, length, errorMsg) { if (value.toString().length > length) { return errorMsg; } }, // 判断手机号 isMobile: function(value, errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判断座机电话 isTel: function(value, errorMsg) { if (!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校验方法 * @param {Array} arr * @return {*} * */ check: function(arr) { var ruleMsg; var checkRule; var _rule; for (var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if (arr[i].el === undefined) { return '没有当前字段' } for (var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { return ruleMsg; } } } }, // 校验所有接口 checkAll: function(arr) { var ruleMsg; var checkRule; var _rule; var reusltMsg = []; for (var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if (arr[i].el === undefined) { return '没有当前字段' } for (var j = 0, ruleLen = arr[i].rules.length; j < ruleLen; j++) { var ruleObj = arr[i].rules[j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { reusltMsg.push({ el: arr[i].el, rules: _rule, message: ruleMsg, alias: arr[i].alias // 绑定一个别名用处:绑定到具体的一个DOM元素上显示错误信息 }) break; // 跳出当前循环,不用把当前一个元素上多个验证不通过结果都存储起来 } } } return reusltMsg.length > 0 ? reusltMsg : false; }, // 添加规则 addRule: function(ruleName, fn) { rules[ruleName] = fn; } } })();
我们调整下html代码:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> <p> <label>请输入用户名:</label> <input type="text" name="userName" /> <span class="error"></span> </p> <p> <label>请输入密码:</label> <input type="text" name="password" /> <span class="error"></span> </p> <p> <label>请输入手机号码:</label> <input type="text" name="phoneNumber" /> <span class="error"></span> </p> <div> <button type="submit">提交</button> </div> </form>
css样式:
<style> .error { color: red; } </style>
submitValidate方法的代码调整为:
function submitValidate() { var registerForm = document.getElementById("registerForm"); validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rulesArr = [{ el: registerForm.userName.value, alias: 'userName', rules: [{ rule: 'isNonEmpty', message: '用户名不能为空' }, { rule: 'minLength:3', message: '用户名长度不能小于3位' }, { rule: 'isAlphaNum', message: '用户名只能是数字跟字母的组合' }] }, { el: registerForm.password.value, alias: 'password', rules: [{ rule: 'isNonEmpty', message: '密码不能为空' }, { rule: 'minLength:6', message: '密码的长度不能小于6位' }] }, { el: registerForm.phoneNumber.value, alias: 'phoneNumber', rules: [{ rule: 'isNonEmpty', message: '手机号不能为空' }, { rule: 'isMobile', message: '手机号码格式不正确' }] } ] var resultMsg = validate.checkAll(rulesArr); if (resultMsg) { for(var re = 0, len = resultMsg.length; re < len; re++) { var curResult = resultMsg[re]; var errorDom = document.querySelector('#registerForm p [name="'+curResult.alias+'"]').nextElementSibling; errorDom.innerHTML = curResult.message; } return false; } return true; }
这样得到的结果就是我们刚才截图的结果了。
兼容失去焦点的方案
如果想兼容失去焦点也触发后面的错误信息提示,暂写了一个草稿代码如下:
var validate = (function() { // 校验规则的各种算法 var rules = { // 判断非空 isNonEmpty: function(value, errorMsg) { if (!value) { return errorMsg; } }, // 判断最小长度 minLength: function(value, length, errorMsg) { if (value.toString().length < length) { return errorMsg; } }, // 判断最大长度 maxLength: function(value, length, errorMsg) { if (value.toString().length > length) { return errorMsg; } }, // 判断手机号 isMobile: function(value, errorMsg) { if (!/(^1[0-9]{10}$)/.test(value)) { return errorMsg; } }, // 判断座机电话 isTel: function(value, errorMsg) { if (!/^\d{3}-d{8}|d{4}-d{7}|d{11}$/.test(value)) { return errorMsg; } } } return { /** 校验方法 * @param {Object} vertifyObj 验证结构如:{userName:[{rule: 'isNonEmpty',message: '用户名不能为空'},{rule: 'minLength:3',message: '用户名长度不能小于3位'}} * @param {Array} arr * @return {*} * */ check: function(vertifyObj,arr) { var ruleMsg; var checkRule; var _rule; for (var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if (arr[i].el === undefined) { return '没有当前字段' } for (var j = 0, ruleLen = vertifyObj[arr[i].alias].length; j < ruleLen; j++) { var ruleObj = vertifyObj[arr[i].alias][j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { return ruleMsg; } } } }, // 校验所有接口 checkAll: function(vertifyObj,arr) { var ruleMsg; var checkRule; var _rule; var reusltMsg = []; for (var i = 0, len = arr.length; i < len; i++) { // 没有当前校验字段 if (arr[i].el === undefined) { return '没有当前字段' } for (var j = 0, ruleLen = vertifyObj[arr[i].alias].length; j < ruleLen; j++) { var ruleObj = vertifyObj[arr[i].alias][j]; checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(arr[i].el); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { reusltMsg.push({ el: arr[i].el, rules: _rule, message: ruleMsg, alias: arr[i].alias // 绑定一个别名用处:绑定到具体的一个DOM元素上显示错误信息 }) break; // 跳出当前循环,不用把当前一个元素上多个验证不通过结果都存储起来 } } } return reusltMsg.length > 0 ? reusltMsg : false; }, // 用户触发验证事件 trigger: function(params) { var self = this; for(var key in params.rules) { if(params.rules.hasOwnProperty(key)) { var requireEl = document.querySelector(params.el + ' [name="'+key+'"]'); var rulesArr = params.rules[key]; var resultRules = rulesArr.filter(function(rule) { if(!rule.trigger || rule.trigger === '') return true; if(Array.isArray(rule.trigger)) { return rule.trigger.indexOf('blur') > -1 } else { return rule.trigger === 'blur'; } }).map(function(rule){return JSON.parse(JSON.stringify(rule))}); (function(dom,curDomRules){ dom.addEventListener('blur',function(event){ var val = dom.value; var ruleMsg = ''; for (var j = 0, ruleLen = curDomRules.length; j < ruleLen; j++) { var ruleObj = curDomRules[j]; var checkRule = ruleObj.rule.split(':'); // rule规则存在minLenth:6这样的校验 var _rule = checkRule.shift(); // 获取校验算法名称 checkRule.unshift(val); // checkRule首位存入校验的value值 checkRule.push(ruleObj.message); // checkRule末尾加入校验的message ruleMsg = rules[_rule].apply(null, checkRule); if (ruleMsg) { var errorDom = dom.nextElementSibling; errorDom.innerHTML = ruleObj.message; break; // 跳出当前循环,不用把当前一个元素上多个验证不通过结果都存储起来 } } if(!ruleMsg) { var errorDom = dom.nextElementSibling; errorDom.innerHTML = ''; } },false); })(requireEl,resultRules); } } }, // 添加规则 addRule: function(ruleName, fn) { rules[ruleName] = fn; } } })(); // 添加-自定义校验算法 validate.addRule('isAlphaNum', function(value, errorMsg) { if (/[^a-zA-Z0-9]/.test(value)) { return errorMsg; } }) var rules = { userName: [ {rule: 'isNonEmpty',message: '用户名不能为空', trigger: 'blur'}, {rule: 'minLength:3',message: '用户名长度不能小于3位', trigger: 'blur'}, {rule: 'isAlphaNum',message: '用户名只能是数字跟字母的组合', trigger: 'blur'} ], password: [ {rule: 'isNonEmpty',message: '密码不能为空', trigger: 'blur'}, {rule: 'minLength:6',message: '密码的长度不能小于6位', trigger: 'blur'} ], phoneNumber: [ {rule: 'isNonEmpty',message: '手机号不能为空', trigger: 'blur'}, {rule: 'isMobile',message: '手机号码格式不正确', trigger: 'blur'} ] } validate.trigger({ el: '#registerForm', rules: rules }) function submitValidate() { var registerForm = document.getElementById("registerForm"); var rulesArr = [{ el: registerForm.userName.value, alias: 'userName' }, { el: registerForm.password.value, alias: 'password' }, { el: registerForm.phoneNumber.value, alias: 'phoneNumber' } ] var resultMsg = validate.checkAll(rules,rulesArr); if (resultMsg) { for(var re = 0, len = resultMsg.length; re < len; re++) { var curResult = resultMsg[re]; var errorDom = document.querySelector('#registerForm p [name="'+curResult.alias+'"]').nextElementSibling; errorDom.innerHTML = curResult.message; } return false; } return true; }
html代码:
<form action="" id="registerForm" method="post" onsubmit="return submitValidate()"> <p> <label>请输入用户名:</label> <input type="text" name="userName" /> <span class="error"></span> </p> <p> <label>请输入密码:</label> <input type="text" name="password" /> <span class="error"></span> </p> <p> <label>请输入手机号码:</label> <input type="text" name="phoneNumber" /> <span class="error"></span> </p> <div> <button type="submit">提交</button> </div> </form>
CSS代码:
<style> .error { color: red; } </style>