上篇文章,主要介绍了重构的一些概念和一些简单的实例。这一次,详细的说下项目中的一个重构场景--给API设计扩展机制。目的就是为了方便之后能灵活应对需求的改变。固然了,是否须要设计扩展性这个要看API的需求。若是你们有什么建议,欢迎评论留言。html
这个能够说是JS里面最原的一个扩展。好比原生JS没有提供打乱数组顺序的API,可是开发者又想方便使用,这样的话,就只能扩展数组的prototype。代码以下vue
//扩展Array.prototype,增长打乱数组的方法。 Array.prototype.upset=function(){ return this.sort((n1,n2)=>Math.random() - 0.5); } let arr=[1,2,3,4,5]; //调用 arr.upset(); //显示结果 console.log(arr);
运行结果jquery
功能是实现了。可是上面的代码,只想借用例子讲解扩展性,你们看下就好。不要模仿,也不要在项目这样写。如今基本都禁止这样开发了。理由也很简单,以前的文章也有提到过。这里重复一下。vue-router
这样就污染了原生对象Array,别人建立的Array也会被污染,形成没必要要的开销。最可怕的是,万一本身命名的跟原生的方法重名了,就被覆盖原来的方法了。设计模式
Array.prototype.push=function(){console.log('守候')} let arrTest=[123] arrTest.push() //result:守候 //push方法有什么做用,你们应该知道,不知道的能够去w3c看下
关于 jQuery 的扩展性,分别提供了三个API:$.extend()、$.fn和$.fn.extend()。分别对jQuery的自己,静态方法,原型对象进行扩展,基于jQuery写插件的时候,最离不开的应该就是$.fn.extend()。数组
参考连接:浏览器
理解jquery的$.extend()、$.fn和$.fn.extend()
Jquery自定义插件之$.extend()、$.fn和$.fn.extend()微信
对VUE进行扩展,引用官网(插件)的说法,扩展的方式通常有如下几种:app
1.添加全局方法或者属性,如: vue-custom-element框架
2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
3.经过全局 mixin 方法添加一些组件选项,如: vue-router
4.添加 Vue 实例方法,经过把它们添加到 Vue.prototype 上实现。
5.一个库,提供本身的 API,同时提供上面提到的一个或多个功能,如 vue-router
基于VUE的扩展。在组件,插件的内容提供一个install方法。以下
使用组件
上面几个扩展性的实例分别是原生对象,库,框架的扩展,你们可能以为有点夸夸而谈,那下面就分享一个平常开发经常使用的一个实例。
看了上面那些扩展性的实例,下面看下一个在平常开发使用得也不少的一个实例:表单验证。这块能够说很简单,可是作好,作通用不简单。看了《JavaScript设计模式与开发实践》,用策略模式对之前的表单验证函数进行了一个重构。下面进行一个简单的分析。
下面的内容,代码会偏多,虽然代码不难,但仍是强烈建议你们不要只看,要边看,边写,边调试,否则做为读者,极可能不知道个人代码是什么意思,很容易懵。下面的代码回涉两个知识:开放-封闭原则和策略模式,你们能够自行了解。
/** * @description 字段检验 * @param checkArr * @returns {boolean} */ function validateForm(checkArr){ let _reg = null, ruleMsg, nullMsg, lenMsg; for (let i = 0, len = checkArr.length; i < len; i++) { //若是没字段值是undefined,再也不执行当前循环,执行下一次循环 if (checkArr[i].el === undefined) { continue; } //设置规则错误提示信息 ruleMsg = checkArr[i].msg || '字段格式错误'; //设置值为空则错误提示信息 nullMsg = checkArr[i].nullMsg || '字段不能为空'; //设置长度错误提示信息 lenMsg = checkArr[i].lenMsg || '字段长度范围' + checkArr[i].minLength + "至" + checkArr[i].maxLength; //若是该字段有空值校验 if (checkArr[i].noNull === true) { //若是字段为空,返回结果又提示信息 if (checkArr[i].el === "" || checkArr[i].el === null) { return nullMsg; } } //若是有该字段有规则校验 if (checkArr[i].rule) { //设置规则 switch (checkArr[i].rule) { case 'mobile': _reg = /^1[3|4|5|7|8][0-9]\d{8}$/; break; case 'tel': _reg = /^\d{3}-\d{8}|\d{4}-\d{7}|\d{11}$/; break; } //若是字段不为空,而且规则错误,返回错误信息 if (!_reg.test(checkArr[i].el) && checkArr[i].el !== "" && checkArr[i].el !== null) { return ruleMsg; } } //若是字段不为空而且长度错误,返回错误信息 if (checkArr[i].el !== null && checkArr[i].el !== '' && (checkArr[i].minLength || checkArr[i].maxLength)) { if (checkArr[i].el.toString().length < checkArr[i].minLength || checkArr[i].el.toString().length > checkArr[i].maxLength) { return lenMsg; } } } return false; }
函数调用方式
let testData={ phone:'18819323632', pwd:'112' } let _tips = validateForm([ {el: testData.phone, noNull: true, nullMsg: '电话号码不能为空',rule: "mobile", msg: '电话号码格式错误'}, {el: testData.pwd, noNull: true, nullMsg: '密码不能为空',lenMsg:'密码长度不正确',minLength:6,maxLength:18} ]); //字段验证若是返回错误信息 if (_tips) { alert(_tips); }
这样方法,相信你们看的也难受,由于问题确实是比较多。
1.一个字段进入,可能要通过三种判断(空值,规则,长度)。若是只是一个简单的电话号码规则校验,就要通过其余两种不必的校验,形成没必要要的开销。运行的流程就如同下面。
2.规则校验里面,只有这几种校验,若是要增长其余校验,好比增长一个日期的规则,没法完成。若是一直修改源码,可能会致使函数巨大。
3.写法不优雅,调用也不方便。
针对上面2-2的三个问题,逐个进行改善。
由于调用方式就不方便,很难在不改变validateForm调用方式的同时,优化重构内部的代码,又增长扩展性。重写这个方法又不可能,由于有个别的地方已经使用了这个API,本身一个一个的改不现实,因此就不修改这个validateForm,新建一个新的API:validate。在之后的项目上,也尽可能引导同事放弃validateForm,使用新的API。
上面第一个,优化校验规则,每次校验(好比空值,长度,规则),都是一个简单的校验,再也不执行其余不必的校验。运行流程如同下面。
let validate = function (arr) { let ruleData = { /** * @description 不能为空 * @param val * @param msg * @return {*} */ isNoNull(val, msg){ if (!val) { return msg } }, /** * @description 最小长度 * @param val * @param length * @param msg * @return {*} */ minLength(val, length, msg){ if (val.toString().length < length) { return msg } }, /** * @description 最大长度 * @param val * @param length * @param msg * @return {*} */ maxLength(val, length, msg){ if (val.toString().length > length) { return msg } }, /** * @description 是不是手机号码格式 * @param val * @param msg * @return {*} */ isMobile(val, msg){ if (!/^1[3-9]\d{9}$/.test(val)) { return msg } } } let ruleMsg, checkRule, _rule; for (let i = 0, len = arr.length; i < len; i++) { //若是字段找不到 if (arr[i].el === undefined) { return '字段找不到!' } //遍历规则 for (let j = 0; j < arr[i].rules.length; j++) { //提取规则 checkRule = arr[i].rules[j].rule.split(":"); _rule = checkRule.shift(); checkRule.unshift(arr[i].el); checkRule.push(arr[i].rules[j].msg); //若是规则错误 ruleMsg = ruleData[_rule].apply(null, checkRule); if (ruleMsg) { //返回错误信息 return ruleMsg; } } } }; let testData = { name: '', phone: '18819522663', pw: 'asda' } //校验函数调用 console.log(validate([ { //校验的数据 el: testData.phone, //校验的规则 rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'} ] }, { el: testData.pw, rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule:'minLength:6',msg:'密码长度不能小于6'} ] } ]));
若是又有其它的规则,又得改这个,这样就违反了开放-封闭原则。若是多人共用这个函数,规则可能会不少,ruleData会变的巨大,形成没必要要的开销。好比A页面有金额的校验,可是只有A页面有。若是按照上面的方式改,在B页面也会加载金额的校验规则,可是根本不会用上,形成资源浪费。
因此下面应用开放-封闭原则。给函数的校验规则增长扩展性。在实操以前,你们应该会懵,由于一个函数,能够进行校验的操做,又有增长校验规则的操做。一个函数作两件事,就违反了单一原则。到时候也难维护,因此推荐的作法就是分接口作。以下写法。
let validate = (function () { let ruleData = { /** * @description 不能为空 * @param val * @param msg * @return {*} */ isNoNull(val, msg){ if (!val) { return msg } }, /** * @description 最小长度 * @param val * @param length * @param msg * @return {*} */ minLength(val, length, msg){ if (val.toString().length < length) { return msg } }, /** * @description 最大长度 * @param val * @param length * @param msg * @return {*} */ maxLength(val, length, msg){ if (val.toString().length > length) { return msg } }, /** * @description 是不是手机号码格式 * @param val * @param msg * @return {*} */ isMobile(val, msg){ if (!/^1[3-9]\d{9}$/.test(val)) { return msg } } } return { /** * @description 查询接口 * @param arr * @return {*} */ check: function (arr) { let ruleMsg, checkRule, _rule; for (let i = 0, len = arr.length; i < len; i++) { //若是字段找不到 if (arr[i].el === undefined) { return '字段找不到!' } //遍历规则 for (let j = 0; j < arr[i].rules.length; j++) { //提取规则 checkRule = arr[i].rules[j].rule.split(":"); _rule = checkRule.shift(); checkRule.unshift(arr[i].el); checkRule.push(arr[i].rules[j].msg); //若是规则错误 ruleMsg = ruleData[_rule].apply(null, checkRule); if (ruleMsg) { //返回错误信息 return ruleMsg; } } } }, /** * @description 添加规则接口 * @param type * @param fn */ addRule:function (type,fn) { ruleData[type]=fn; } } })(); //校验函数调用-测试用例 console.log(validate.check([ { //校验的数据 el: testData.mobile, //校验的规则 rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'} ] }, { el: testData.password, rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule:'minLength:6',msg:'密码长度不能小于6'} ] } ])); //扩展-添加日期范围校验 validate.addRule('isDateRank',function (val,msg) { if(new Date(val[0]).getTime()>=new Date(val[1]).getTime()){ return msg; } }); //测试新添加的规则-日期范围校验 console.log(validate.check([ { el:['2017-8-9 22:00:00','2017-8-8 24:00:00'], rules:[{ rule:'isDateRank',msg:'日期范围不正确' }] } ]));
如上代码所示,这里须要往ruleData添加日期范围的校验,这里能够添加。可是不能访问和修改ruleData的东西,有一个保护的做用。还有一个就是,好比在A页面添加日期的校验,只在A页面存在,不会影响其它页面。若是日期的校验在其它地方均可能用上,就能够考虑,在全局里面为ruleData添加日期的校验的规则。
至于第三个问题,这样的想法,可能不算太优雅,调用也不是太方便,可是就我如今能想到的,这个就是最好方案啊了。
这个看似是已经作完了,可是你们可能以为有一种状况没能应对,好比下面这种,作不到。
由于上面的check接口,只要有一个错误了,就立马跳出了,不会校验下一个。若是要实现下面的功能,就得实现,若是有一个值校验错误,就记录错误信息,继续校验下一个,等到全部的校验都执行完了以后,以下面的流程图。
代码上面(你们先忽略alias这个属性)
let validate= (function () { let ruleData = { /** * @description 不能为空 * @param val * @param msg * @return {*} */ isNoNull(val, msg){ if (!val) { return msg } }, /** * @description 最小长度 * @param val * @param length * @param msg * @return {*} */ minLength(val, length, msg){ if (val.toString().length < length) { return msg } }, /** * @description 最大长度 * @param val * @param length * @param msg * @return {*} */ maxLength(val, length, msg){ if (val.toString().length > length) { return msg } }, /** * @description 是不是手机号码格式 * @param val * @param msg * @return {*} */ isMobile(val, msg){ if (!/^1[3-9]\d{9}$/.test(val)) { return msg } } } return { check: function (arr) { //代码不重复展现,上面一部分 }, addRule:function (type,fn) { //代码不重复展现,上面一部分 }, /** * @description 校验全部接口 * @param arr * @return {*} */ checkAll: function (arr) { let ruleMsg, checkRule, _rule,msgArr=[]; for (let i = 0, len = arr.length; i < len; i++) { //若是字段找不到 if (arr[i].el === undefined) { return '字段找不到!' } //若是字段为空以及规则不是校验空的规则 //遍历规则 for (let j = 0; j < arr[i].rules.length; j++) { //提取规则 checkRule = arr[i].rules[j].rule.split(":"); _rule = checkRule.shift(); checkRule.unshift(arr[i].el); checkRule.push(arr[i].rules[j].msg); //若是规则错误 ruleMsg = ruleData[_rule].apply(null, checkRule); if (ruleMsg) { //记录错误信息 msgArr.push({ el:arr[i].el, alias:arr[i].alias, rules:_rule, msg:ruleMsg }); } } } //返回错误信息 return msgArr.length>0?msgArr:false; } } })(); let testData = { name: '', phone: '188', pw: 'asda' } //扩展-添加日期范围校验 validate.addRule('isDateRank',function (val,msg) { if(new Date(val[0]).getTime()>=new Date(val[1]).getTime()){ return msg; } }); //校验函数调用 console.log(validate.checkAll([ { //校验的数据 el: testData.phone, alias:'mobile', //校验的规则 rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule: 'isMobile', msg: '手机号码格式不正确'},{rule:'minLength:6',msg: '手机号码不能少于6'} ] }, { el: testData.pw, alias:'pwd', rules: [ {rule: 'isNoNull', msg: '电话不能为空'}, {rule:'minLength:6',msg:'密码长度不能小于6'} ] }, { el:['2017-8-9 22:00:00','2017-8-8 24:00:00'], rules:[{ rule:'isDateRank',msg:'日期范围不正确' }] } ]));
看到结果,如今全部的不合法的数据的记录都返回回来了。至于当时alias如今揭晓用处。
好比页面是vue渲染的,根据alias能够这样处理。
若是是jQuery渲染的,根据alias能够这样处理。
由于项目以前有使用了之前的校验API,不能一道切,在之前的API没废弃以前,不能影响以前的使用。因此要重写之前的validateForm,使之兼容如今的新API:validate。
let validateForm=function (arr) { let _param=[],_single={}; for(let i=0;i<arr.length;i++){ _single={}; _single.el=arr[i].el; _single.rules=[]; //若有有非空检验 if(arr[i].noNull){ _single.rules.push({ rule: 'isNoNull', msg: arr[i].nullMsg||'字段不能为空' }) } //若是有最小长度校验 if(arr[i].minLength){ _single.rules.push({ rule: 'minLength:'+arr[i].minLength, msg: arr[i].lenMsg ||'字段长度范围错误' }) } //若是有最大长度校验 if(arr[i].maxLength){ _single.rules.push({ rule: 'maxLength:'+arr[i].maxLength, msg: arr[i].lenMsg ||'字段长度范围错误' }) } //若是有规则校验 //校验转换规则 let _ruleData={ mobile:'isMobile' } if(arr[i].rule){ _single.rules.push({ rule: _ruleData[arr[i].rule], msg: arr[i].msg ||'字段格式错误' }) } _param.push(_single); } let _result=validate.check(_param); return _result?_result:false; } let testData={ phone:'18819323632', pwd:'112' } let _tips = validateForm([ {el: testData.phone, noNull: true, nullMsg: '电话号码不能为空',rule: "mobile", msg: '电话号码格式错误'}, {el: testData.pwd, noNull: true, nullMsg: '密码不能为空',lenMsg:'密码长度不正确',minLength:6,maxLength:18} ]); console.log(_tips)
今天的例子就到这里了,这个例子,无非就是给API增长扩展性。这个例子比较简单,不算难。你们用这个代码在浏览器上运行,就很好理解。若是你们对这个例子有什么更好的建议,或者代码上有什么问题,欢迎在评论区留言,你们多交流,相互学习。
-------------------------华丽的分割线--------------------
想了解更多,关注关注个人微信公众号:守候书阁