1. Введение
Последняя статья в основном представила некоторые концепции рефакторинга и несколько простых примеров. На этот раз подробно поговорим о сценарии рефакторинга в проекте — проектировании механизма расширения для API. Цель состоит в том, чтобы облегчить гибкость реагирования на изменения спроса в будущем. Конечно, необходимость разработки расширяемости зависит от потребностей API. Если у вас есть какие-либо предложения, пожалуйста, оставьте комментарий.
2. Расширяемое представление
2-1.prototype
Можно сказать, что это самое оригинальное расширение в JS. Например, нативный JS не предоставляет API для перетасовки порядка массивов, но разработчики хотят использовать его удобно, в этом случае они могут только расширить прототип массива. код показывает, как показано ниже
//扩展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);
результат операции
реализуется функция. Но приведенный выше код я просто хочу использовать в качестве примера для объяснения масштабируемости, просто посмотрите на него. Не подражайте и не пишите об этом в проекте. В принципе сейчас это делать запрещено. Причина тоже очень проста, как упоминалось в предыдущей статье. Повторите здесь.
Это загрязняет собственный массив объектов, и массив, созданный другими, также будет загрязнен, вызывая ненужные накладные расходы. Самое ужасное, что в случае, если тот, который вы называете, совпадает с нативным методом, оригинальный метод будет перезаписан.
Array.prototype.push=function(){console.log('守候')}
let arrTest=[123]
arrTest.push()
//result:守候
//push方法有什么作用,大家应该知道,不知道的可以去w3c看下
2-2.jQuery
Что касается расширяемости jQuery, предоставляются три API: $.extend(), $.fn и $.fn.extend(). Расширяйте сам jQuery, статические методы и объекты-прототипы соответственно.
Ссылка на ссылку:
Понимание jquery $.extend(), $.fn и $.fn.extend()
$.extend(), $.fn и $.fn.extend() пользовательского плагина Jquery
2-3.VUE
Чтобы расширить VUE, обратитесь к официальному сайту (плагин), методы расширения обычно включают следующее:
1. Добавьте глобальные методы или свойства, например: vue-custom-element
2. Добавьте глобальные ресурсы: директивы/фильтры/переходы и т. д., например vue-touch
3. Добавьте некоторые параметры компонента через глобальный метод миксина, например: vue-router
4. Добавьте методы экземпляра Vue, добавив их в Vue.prototype.
5. Библиотека, которая предоставляет собственный API, предоставляя при этом одну или несколько функций, упомянутых выше, например, vue-router.
Расширение на основе VUE. В компоненте содержимое плагина предоставляет метод установки. следующим образом
использовать компоненты
Приведенные выше примеры расширяемости являются расширениями нативных объектов, библиотек и фреймворков. Вам может показаться, что это немного преувеличено, поэтому давайте поделимся примером, который обычно используется в повседневной разработке.
3. Пример — проверка формы
После рассмотрения приведенных выше примеров расширяемости давайте взглянем на следующий пример, который также часто используется в повседневной разработке: проверка формы. Это можно сказать очень просто, но сделать это хорошо и сделать универсальным непросто. После прочтения «Шаблоны проектирования JavaScript и практика разработки» я реорганизовал предыдущую функцию проверки формы с помощью шаблона стратегии. Ниже проводится простой анализ.
Следующее содержание, кода будет слишком много, хотя код не сложный, но я настоятельно рекомендую вам не просто читать, читать, писать и отлаживать, иначе, как читатель, вы, вероятно, не знаете, что мой код означает, что это просто глупо. Следующий код будет включать в себя два знания: принцип открытия-закрытия и шаблон стратегии, которые вы можете понять сами.
3-1.Первоначальный план
/**
* @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);
}
3-2.Есть проблема
Этот способ, я считаю, всем неудобен, потому что проблем действительно много.
1. При вводе поля может потребоваться пройти через три суждения (нулевое значение, правило, длина). Если это простая проверка правила телефонного номера, она пройдет через две другие ненужные проверки, что приведет к ненужным накладным расходам. Ходовой процесс выглядит следующим образом.
2. В проверке правила есть только такие проверки.Если вы хотите добавить другие проверки, например, добавить правило даты, это не может быть завершено. Если вы продолжите изменять исходный код, это может привести к тому, что функция станет огромной.
3. Способ написания не изящный, а вызов не удобный.
3-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 недостаточно, например, если я хочу добавить проверку диапазона дат, то я должен изменить ruleData и добавьте атрибут. следующим образом
let ruleData = {
//之前的一些规则
/**
* @description 是否是日期范围
* @param val
* @param msg
* @return {*}
*/
isDateRank(val,msg) {
let _date=val.split(',');
if(new Date(_date[0]).getTime()>=new Date(_date[1]).getTime()){
return msg;
}
}
}
Если есть другие правила, это нужно изменить, что нарушает принцип открытого-закрытого. Если несколько человек используют эту функцию, может быть много правил, и ruleData станет огромным, вызывая ненужные накладные расходы. Например, на странице А есть проверка суммы, но она есть только на странице А. Если изменить его вышеописанным способом, правило проверки суммы также будет загружено на странице 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, имеет защитный эффект. Другой заключается в том, что, например, добавление проверки даты на странице А будет существовать только на странице А и не повлияет на другие страницы. Если проверка даты может использоваться в других местах, вы можете рассмотреть возможность добавления правила проверки даты в ruleData в файле global.
Что касается третьего вопроса, то такая идея может быть не слишком элегантной, а звонок не слишком удобным, но насколько я сейчас могу придумать, это лучшее решение.
Кажется, это было сделано, но вы можете почувствовать, что есть ситуация, с которой нельзя справиться, например следующая, которую нельзя сделать.
Из-за вышеуказанного интерфейса проверки, пока есть ошибка, она сразу выскочит и не будет проверять следующую. Если вы хотите реализовать следующие функции, вы должны реализовать их.Если есть ошибка проверки значения, запишите информацию об ошибке, продолжите проверку следующей и дождитесь завершения всех проверок, как показано на следующей блок-схеме.
После завершения выполнения результат возвращается вместе, затем интерфейс должен быть представлен ниже.
Код выглядит следующим образом (сначала мы игнорируем атрибут псевдонима)
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:'日期范围不正确'
}]
}
]));
Увидев результат, теперь возвращаются все записи недопустимых данных. Что касается псевдонима в то время, то его использование теперь раскрыто.
Например, страница отображается с помощью vue, который может обрабатываться в соответствии с псевдонимом.
Если он отображается с помощью jQuery, он может обрабатываться таким образом в соответствии с псевдонимом.
3-4 Схема обратной совместимости
Поскольку проект ранее использовал предыдущий 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)
4. Резюме
Вот и все, что касается сегодняшнего примера.Это не что иное, как добавление расширяемости к API. Этот пример относительно прост, не сложен. Каждый использует этот код для работы в браузере, и его легко понять. Если у вас есть какие-либо лучшие предложения для этого примера или если есть какие-либо проблемы с кодом, оставьте сообщение в области комментариев, чтобы мы могли больше общаться и учиться друг у друга.
------------------------- Великолепная разделительная линия --------------------
Хотите узнать больше, обратите внимание на мой публичный аккаунт WeChat: В ожидании книжного магазина