前端正則表達式表單驗證與字符串處理高頻場景實戰(zhàn)合集
1. 正則表達式基礎(chǔ)回顧
1.1 基本概念
正則表達式(Regular Expression)是用于匹配字符串中字符組合的模式。在JavaScript中,我們可以使用兩種方式創(chuàng)建正則表達式:
// 字面量方式
const regex1 = /abc/;
// 構(gòu)造函數(shù)方式
const regex2 = new RegExp('abc');
1.2 常用元字符
| 元字符 | 描述 | 示例 |
|---|---|---|
. | 匹配除換行符外的任意字符 | /a.c/ 匹配 “abc”, “aac” |
^ | 匹配字符串的開始 | /^abc/ 匹配以abc開頭的字符串 |
$ | 匹配字符串的結(jié)束 | /abc$/ 匹配以abc結(jié)尾的字符串 |
* | 匹配前面的子表達式零次或多次 | /ab*c/ 匹配 “ac”, “abc”, “abbc” |
+ | 匹配前面的子表達式一次或多次 | /ab+c/ 匹配 “abc”, “abbc” |
? | 匹配前面的子表達式零次或一次 | /ab?c/ 匹配 “ac”, “abc” |
[] | 字符集合,匹配其中任意一個字符 | /[abc]/ 匹配 “a”, “b”, “c” |
\d | 匹配數(shù)字,等價于[0-9] | /\d+/ 匹配一個或多個數(shù)字 |
\w | 匹配字母、數(shù)字、下劃線 | /\w+/ 匹配單詞字符 |
\s | 匹配空白字符 | /\s+/ 匹配一個或多個空白 |
2. 表單驗證場景實戰(zhàn)
2.1 郵箱驗證
郵箱驗證是前端開發(fā)中最常見的需求之一。一個完善的郵箱正則應(yīng)該考慮各種格式:
/**
* 郵箱驗證 - 基礎(chǔ)版本
* 適合大多數(shù)場景
*/
function validateEmailBasic(email) {
const regex = /^[\w.-]+@[\w.-]+\.\w+$/;
return regex.test(email);
}
/**
* 郵箱驗證 - 嚴格版本
* 符合RFC 5322標準的大部分規(guī)則
*/
function validateEmailStrict(email) {
const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return regex.test(email);
}
/**
* 郵箱驗證 - 實用版本
* 平衡準確性和性能
*/
function validateEmailPractical(email) {
const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return regex.test(email);
}
// 測試用例
console.log(validateEmailBasic('user@example.com')); // true
console.log(validateEmailBasic('user.name@example.com')); // true
console.log(validateEmailBasic('user@example.co.uk')); // true
console.log(validateEmailBasic('invalid.email')); // false
2.2 手機號驗證
不同國家的手機號格式不同,這里以中國手機號為例:
/**
* 中國手機號驗證
* 支持最新的號段
*/
function validateChinesePhone(phone) {
const regex = /^1[3-9]\d{9}$/;
return regex.test(phone);
}
/**
* 帶區(qū)號的手機號驗證
*/
function validatePhoneWithAreaCode(phone) {
const regex = /^(?:\+86)?1[3-9]\d{9}$/;
return regex.test(phone);
}
/**
* 固話號碼驗證
*/
function validateLandline(phone) {
const regex = /^(?:0[1-9]\d{1,2}-)?[2-8]\d{6,7}$/;
return regex.test(phone);
}
// 測試用例
console.log(validateChinesePhone('13800138000')); // true
console.log(validateChinesePhone('+8613800138000')); // false
console.log(validatePhoneWithAreaCode('+8613800138000')); // true
console.log(validateLandline('010-12345678')); // true
2.3 身份證號驗證
中國大陸的身份證號有嚴格的校驗規(guī)則:
/**
* 身份證號驗證
* 包含15位和18位身份證號的驗證
*/
function validateIDCard(idCard) {
// 基本格式驗證
const regex15 = /^[1-9]\d{7}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}$/;
const regex18 = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/;
if (idCard.length === 15) {
return regex15.test(idCard);
} else if (idCard.length === 18) {
if (!regex18.test(idCard)) return false;
// 校驗碼驗證
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(idCard[i]) * weights[i];
}
const checkCode = checkCodes[sum % 11];
return idCard[17].toUpperCase() === checkCode;
}
return false;
}
// 測試用例
console.log(validateIDCard('11010519900307283X')); // true
console.log(validateIDCard('110105900307283')); // true
console.log(validateIDCard('123456789012345')); // false
2.4 密碼強度驗證
密碼強度驗證通常需要滿足多個條件:
/**
* 密碼強度驗證 - 基礎(chǔ)版本
*/
function validatePasswordBasic(password) {
// 至少8位,包含字母和數(shù)字
const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/;
return regex.test(password);
}
/**
* 密碼強度驗證 - 中級版本
* 必須包含大小寫字母、數(shù)字、特殊字符中的至少3種
*/
function validatePasswordMedium(password) {
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return regex.test(password);
}
/**
* 密碼強度驗證 - 高級版本
* 自定義規(guī)則,可配置不同強度等級
*/
function validatePasswordAdvanced(password, options = {}) {
const {
minLength = 8,
maxLength = 20,
requireUppercase = true,
requireLowercase = true,
requireNumbers = true,
requireSpecialChars = false,
specialChars = '@$!%*?&'
} = options;
if (password.length < minLength || password.length > maxLength) {
return false;
}
let pattern = '^';
if (requireLowercase) pattern += '(?=.*[a-z])';
if (requireUppercase) pattern += '(?=.*[A-Z])';
if (requireNumbers) pattern += '(?=.*\\d)';
if (requireSpecialChars) pattern += `(?=.*[${specialChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}])`;
pattern += `[A-Za-z\\d${specialChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]{${minLength},${maxLength}}$`;
const regex = new RegExp(pattern);
return regex.test(password);
}
// 測試用例
console.log(validatePasswordBasic('password123')); // true
console.log(validatePasswordMedium('Password123!')); // true
console.log(validatePasswordAdvanced('mypassword', {requireUppercase: false})); // true
2.5 URL驗證
URL格式驗證需要考慮多種協(xié)議和格式:
/**
* URL驗證 - 基礎(chǔ)版本
*/
function validateURLBasic(url) {
const regex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
return regex.test(url);
}
/**
* URL驗證 - 完整版本
* 支持更多協(xié)議和格式
*/
function validateURLComplete(url) {
const regex = /^(https?|ftp):\/\/((([\w-]+\.)+[\w-]+)|localhost)(:[0-9]+)?(\/[\w- .\/?%&=]*)?$/;
return regex.test(url);
}
/**
* 提取URL中的域名
*/
function extractDomain(url) {
const regex = /^(?:https?:\/\/)?(?:www\.)?([^\/]+)/;
const match = url.match(regex);
return match ? match[1] : null;
}
// 測試用例
console.log(validateURLBasic('https://www.example.com')); // true
console.log(validateURLBasic('http://example.com/path/to/page')); // true
console.log(extractDomain('https://www.example.com/path')); // www.example.com
2.6 IP地址驗證
IP地址驗證包括IPv4和IPv6:
/**
* IPv4地址驗證
*/
function validateIPv4(ip) {
const regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return regex.test(ip);
}
/**
* IPv6地址驗證
*/
function validateIPv6(ip) {
const regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
return regex.test(ip);
}
/**
* 通用IP地址驗證
*/
function validateIP(ip) {
return validateIPv4(ip) || validateIPv6(ip);
}
// 測試用例
console.log(validateIPv4('192.168.1.1')); // true
console.log(validateIPv4('256.1.1.1')); // false
console.log(validateIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // true
3. 字符串處理高頻場景
3.1 HTML標簽清理
在處理富文本內(nèi)容時,經(jīng)常需要清理HTML標簽:
/**
* 移除所有HTML標簽
*/
function removeAllHTMLTags(html) {
return html.replace(/<[^>]*>/g, '');
}
/**
* 移除指定HTML標簽
*/
function removeSpecificHTMLTags(html, tags) {
const tagPattern = tags.map(tag => `<${tag}[^>]*>|<\\/${tag}>`).join('|');
const regex = new RegExp(tagPattern, 'gi');
return html.replace(regex, '');
}
/**
* 保留指定HTML標簽,移除其他標簽
*/
function keepSpecificHTMLTags(html, allowedTags) {
const allowedPattern = allowedTags.map(tag => `<${tag}[^>]*>|<\\/${tag}>`).join('|');
const allTagsPattern = /<[^>]*>/g;
return html.replace(allTagsPattern, (match) => {
const regex = new RegExp(allowedPattern, 'i');
return regex.test(match) ? match : '';
});
}
/**
* 轉(zhuǎn)義HTML特殊字符
*/
function escapeHTML(html) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return html.replace(/[&<>"']/g, (match) => escapeMap[match]);
}
// 測試用例
const htmlContent = '<div><p>Hello <script>alert("xss")</script>World!</p></div>';
console.log(removeAllHTMLTags(htmlContent)); // "Hello World!"
console.log(removeSpecificHTMLTags(htmlContent, ['script'])); // "<div><p>Hello World!</p></div>"
console.log(keepSpecificHTMLTags(htmlContent, ['p'])); // "<p>Hello World!</p>"
3.2 特殊字符過濾
處理用戶輸入時,經(jīng)常需要過濾特殊字符:
/**
* 移除非字母數(shù)字字符
*/
function removeNonAlphanumeric(str) {
return str.replace(/[^a-zA-Z0-9]/g, '');
}
/**
* 移除非中文字符
*/
function removeNonChinese(str) {
return str.replace(/[^\u4e00-\u9fa5]/g, '');
}
/**
* 移除非ASCII字符
*/
function removeNonASCII(str) {
return str.replace(/[^\x00-\x7F]/g, '');
}
/**
* 自定義字符過濾
*/
function filterCustomCharacters(str, allowedChars) {
const pattern = `[^${allowedChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]`;
const regex = new RegExp(pattern, 'g');
return str.replace(regex, '');
}
/**
* 移除非打印字符
*/
function removeNonPrintable(str) {
return str.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
}
// 測試用例
console.log(removeNonAlphanumeric('Hello, 世界! 123')); // "Hello123"
console.log(removeNonChinese('Hello, 世界! 123')); // "世界"
console.log(filterCustomCharacters('Hello123!@#', 'a-zA-Z')); // "Hello"
3.3 數(shù)字格式化
數(shù)字格式化在金融、電商等場景中非常常見:
/**
* 千位分隔符格式化
*/
function formatNumberWithCommas(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
/**
* 貨幣格式化
*/
function formatCurrency(amount, currency = 'CNY') {
const formatted = formatNumberWithCommas(amount);
const symbols = {
'CNY': '¥',
'USD': '$',
'EUR': '€',
'GBP': '£'
};
return `${symbols[currency] || ''}${formatted}`;
}
/**
* 提取字符串中的數(shù)字
*/
function extractNumbers(str) {
const matches = str.match(/\d+(?:\.\d+)?/g);
return matches ? matches.map(Number) : [];
}
/**
* 科學(xué)計數(shù)法轉(zhuǎn)普通數(shù)字
*/
function scientificToDecimal(numStr) {
const match = numStr.match(/^(\d+(?:\.\d+)?)[eE]([+-]?\d+)$/);
if (!match) return numStr;
const [, digits, exponent] = match;
const exp = parseInt(exponent, 10);
const [integer, decimal = ''] = digits.split('.');
if (exp >= 0) {
return integer + decimal.padEnd(exp + decimal.length, '0');
} else {
const totalLength = integer.length + Math.abs(exp);
const result = (integer + decimal).padStart(totalLength, '0');
return '0.' + result.slice(0, totalLength);
}
}
// 測試用例
console.log(formatNumberWithCommas(1234567)); // "1,234,567"
console.log(formatCurrency(1234567, 'USD')); // "$1,234,567"
console.log(extractNumbers('價格是123.45元,優(yōu)惠了20%')); // [123.45, 20]
console.log(scientificToDecimal('1.23e-3')); // "0.00123"
3.4 字符串提取
從復(fù)雜文本中提取有用信息是正則表達式的強項:
/**
* 提取URL參數(shù)
*/
function extractURLParams(url) {
const params = {};
const match = url.match(/\?([^#]+)/);
if (match) {
const searchParams = new URLSearchParams(match[1]);
for (const [key, value] of searchParams) {
params[key] = value;
}
}
return params;
}
/**
* 提取CSS屬性
*/
function extractCSSProperties(cssText) {
const properties = {};
const regex = /([a-z-]+)\s*:\s*([^;]+);?/gi;
let match;
while ((match = regex.exec(cssText)) !== null) {
properties[match[1].trim()] = match[2].trim();
}
return properties;
}
/**
* 提取JSON字符串
*/
function extractJSON(str) {
const jsonRegex = /\{[^{}]*\}/g;
const matches = str.match(jsonRegex);
if (!matches) return [];
return matches.filter(match => {
try {
JSON.parse(match);
return true;
} catch {
return false;
}
}).map(json => JSON.parse(json));
}
/**
* 提取Markdown鏈接
*/
function extractMarkdownLinks(markdown) {
const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
const links = [];
let match;
while ((match = regex.exec(markdown)) !== null) {
links.push({
text: match[1],
url: match[2]
});
}
return links;
}
// 測試用例
const url = 'https://example.com?name=John&age=30#section';
console.log(extractURLParams(url)); // { name: 'John', age: '30' }
const css = 'color: red; font-size: 16px; margin: 10px 20px;';
console.log(extractCSSProperties(css)); // { color: 'red', 'font-size': '16px', margin: '10px 20px' }
const markdown = 'Check out [Google](https://google.com) and [GitHub](https://github.com)';
console.log(extractMarkdownLinks(markdown)); // [{ text: 'Google', url: 'https://google.com' }, ...]
3.5 文本替換
文本替換是正則表達式的核心功能之一:
/**
* 智能大小寫轉(zhuǎn)換
*/
function smartCase(str, type = 'camel') {
switch (type) {
case 'camel':
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
case 'pascal':
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, word => word.toUpperCase()).replace(/\s+/g, '');
case 'kebab':
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().replace(/\s+/g, '-');
case 'snake':
return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase().replace(/\s+/g, '_');
default:
return str;
}
}
/**
* 電話號碼格式化
*/
function formatPhoneNumber(phone, format = 'national') {
const cleaned = phone.replace(/\D/g, '');
switch (format) {
case 'national':
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
case 'international':
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '+86 $1 $2 $3');
case 'dash':
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
default:
return cleaned;
}
}
/**
* 敏感詞過濾
*/
function filterSensitiveWords(text, sensitiveWords, replacement = '***') {
if (!Array.isArray(sensitiveWords) || sensitiveWords.length === 0) {
return text;
}
const pattern = sensitiveWords.map(word => {
return word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}).join('|');
const regex = new RegExp(pattern, 'gi');
return text.replace(regex, replacement);
}
/**
* 日期格式化
*/
function formatDateString(dateStr, format = 'YYYY-MM-DD') {
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
}
// 測試用例
console.log(smartCase('hello world', 'camel')); // "helloWorld"
console.log(smartCase('hello world', 'pascal')); // "HelloWorld"
console.log(formatPhoneNumber('13800138000', 'dash')); // "138-0013-8000"
console.log(filterSensitiveWords('這是一段包含敏感詞的文本', ['敏感詞'], '[已過濾]')); // "這是一段包含[已過濾]的文本"
4. 性能優(yōu)化技巧
正則表達式的性能對大型應(yīng)用至關(guān)重要:
4.1 避免回溯災(zāi)難
/**
* 性能對比:貪婪 vs 懶惰匹配
*/
function comparePerformance() {
const testString = 'a'.repeat(10000) + 'b';
// 貪婪匹配 - 可能導(dǎo)致性能問題
const greedyRegex = /a.*b/;
// 懶惰匹配 - 更好的性能
const lazyRegex = /a.*?b/;
// 獨占匹配 - 最佳性能
const possessiveRegex = /a[^b]*b/;
console.time('貪婪匹配');
greedyRegex.test(testString);
console.timeEnd('貪婪匹配');
console.time('懶惰匹配');
lazyRegex.test(testString);
console.timeEnd('懶惰匹配');
console.time('獨占匹配');
possessiveRegex.test(testString);
console.timeEnd('獨占匹配');
}
4.2 預(yù)編譯正則表達式
/**
* 預(yù)編譯正則表達式緩存
*/
class RegexCache {
constructor() {
this.cache = new Map();
}
get(pattern, flags = '') {
const key = `${pattern}_${flags}`;
if (!this.cache.has(key)) {
this.cache.set(key, new RegExp(pattern, flags));
}
return this.cache.get(key);
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
}
// 使用示例
const regexCache = new RegexCache();
const emailRegex = regexCache.get('^[\\w.-]+@[\\w.-]+\\.\\w+$');
console.log(emailRegex.test('user@example.com')); // true
4.3 使用合適的量詞
/**
* 選擇合適的量詞
*/
const optimizations = {
// 使用具體的量詞而不是通用量詞
specific: {
phone: /^\d{11}$/, // 而不是 /^\d+$/
postalCode: /^\d{6}$/, // 而不是 /^\d+$/
},
// 使用原子組減少回溯
atomic: {
// 使用 (?>...) 原子組
pattern: /(?>a+)b/,
},
// 使用 possessive 量詞(在某些正則引擎中)
possessive: {
// JavaScript 不直接支持,但可以通過字符類模擬
pattern: /a[^a]*b/,
}
};
5. 常見錯誤與調(diào)試方法
5.1 常見錯誤
/**
* 常見錯誤示例
*/
const commonMistakes = {
// 1. 忘記轉(zhuǎn)義特殊字符
unescapedDot: /www.example.com/, // 應(yīng)該為 /www\.example\.com/
// 2. 使用錯誤的字符類
wrongCharClass: /[a-zA-Z0-9_]/, // 應(yīng)該使用 /\w/
// 3. 貪婪匹配導(dǎo)致的問題
greedyProblem: /<.*>/, // 會匹配整個字符串,應(yīng)該使用 /<.*?>/
// 4. 忘記全局標志
noGlobalFlag: 'hello world'.replace(/o/, '0'), // 只替換第一個
// 5. 錯誤的邊界匹配
wrongBoundary: /word/, // 會匹配 "password" 中的 "word"
};
5.2 調(diào)試工具和方法
/**
* 正則表達式調(diào)試工具
*/
class RegexDebugger {
static test(regex, testCases) {
console.log(`測試正則表達式: ${regex}`);
console.log('='.repeat(50));
testCases.forEach(testCase => {
const result = regex.test(testCase);
console.log(`"${testCase}" -> ${result}`);
});
}
static match(regex, str) {
console.log(`在 "${str}" 中匹配 ${regex}`);
console.log('='.repeat(50));
const matches = str.match(regex);
if (matches) {
console.log('匹配結(jié)果:', matches);
// 顯示捕獲組
const execResult = regex.exec(str);
if (execResult && execResult.length > 1) {
console.log('捕獲組:');
for (let i = 1; i < execResult.length; i++) {
console.log(` 組 ${i}: ${execResult[i]}`);
}
}
} else {
console.log('無匹配結(jié)果');
}
}
static stepByStep(regex, str) {
console.log(`逐步匹配: ${regex} vs "${str}"`);
console.log('='.repeat(50));
let match;
const globalRegex = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
while ((match = globalRegex.exec(str)) !== null) {
console.log(`找到匹配: "${match[0]}" 在位置 ${match.index}`);
console.log(`剩余字符串: "${str.slice(match.index + match[0].length)}"`);
}
}
}
// 使用示例
const emailRegex = /^[\w.-]+@[\w.-]+\.\w+$/;
const testEmails = [
'user@example.com',
'invalid.email',
'user.name@example.co.uk',
'@example.com',
'user@'
];
RegexDebugger.test(emailRegex, testEmails);
6. 實戰(zhàn)項目:表單驗證庫
讓我們綜合運用所學(xué)知識,創(chuàng)建一個完整的表單驗證庫:
/**
* 表單驗證庫
*/
class FormValidator {
constructor() {
this.rules = new Map();
this.customValidators = new Map();
this.setupDefaultRules();
}
setupDefaultRules() {
// 內(nèi)置驗證規(guī)則
this.rules.set('required', {
pattern: /./,
message: '此字段為必填項'
});
this.rules.set('email', {
pattern: /^[\w.-]+@[\w.-]+\.\w+$/,
message: '請輸入有效的郵箱地址'
});
this.rules.set('phone', {
pattern: /^1[3-9]\d{9}$/,
message: '請輸入有效的手機號碼'
});
this.rules.set('idCard', {
pattern: /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]$/,
message: '請輸入有效的身份證號碼'
});
this.rules.set('url', {
pattern: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
message: '請輸入有效的URL地址'
});
this.rules.set('number', {
pattern: /^\d+(\.\d+)?$/,
message: '請輸入有效的數(shù)字'
});
this.rules.set('integer', {
pattern: /^\d+$/,
message: '請輸入有效的整數(shù)'
});
this.rules.set('chinese', {
pattern: /^[\u4e00-\u9fa5]+$/,
message: '只能輸入中文'
});
this.rules.set('english', {
pattern: /^[a-zA-Z]+$/,
message: '只能輸入英文'
});
this.rules.set('postalCode', {
pattern: /^\d{6}$/,
message: '請輸入有效的郵政編碼'
});
}
/**
* 添加自定義驗證規(guī)則
*/
addRule(name, pattern, message) {
this.rules.set(name, {
pattern: pattern instanceof RegExp ? pattern : new RegExp(pattern),
message: message
});
return this;
}
/**
* 添加自定義驗證器
*/
addValidator(name, validator) {
if (typeof validator !== 'function') {
throw new Error('驗證器必須是函數(shù)');
}
this.customValidators.set(name, validator);
return this;
}
/**
* 驗證單個值
*/
validate(value, rules) {
const errors = [];
if (!Array.isArray(rules)) {
rules = [rules];
}
for (const rule of rules) {
const result = this.validateRule(value, rule);
if (result !== true) {
errors.push(result);
}
}
return {
valid: errors.length === 0,
errors: errors
};
}
/**
* 驗證單個規(guī)則
*/
validateRule(value, rule) {
// 處理必填驗證
if (rule === 'required' || (typeof rule === 'object' && rule.type === 'required')) {
if (value === null || value === undefined || value === '') {
const message = typeof rule === 'object' && rule.message ? rule.message : this.rules.get('required').message;
return message;
}
return true;
}
// 如果值為空且不是必填項,跳過驗證
if (value === null || value === undefined || value === '') {
return true;
}
// 處理內(nèi)置規(guī)則
if (typeof rule === 'string') {
const ruleConfig = this.rules.get(rule);
if (!ruleConfig) {
throw new Error(`未知的驗證規(guī)則: ${rule}`);
}
if (!ruleConfig.pattern.test(value)) {
return ruleConfig.message;
}
return true;
}
// 處理正則表達式
if (rule instanceof RegExp) {
if (!rule.test(value)) {
return '格式不正確';
}
return true;
}
// 處理對象形式的規(guī)則
if (typeof rule === 'object') {
// 處理正則表達式規(guī)則
if (rule.pattern) {
const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern);
if (!pattern.test(value)) {
return rule.message || '格式不正確';
}
return true;
}
// 處理自定義驗證器
if (rule.validator) {
const result = rule.validator(value);
if (result !== true) {
return result || '驗證失敗';
}
return true;
}
// 處理內(nèi)置規(guī)則
if (rule.type) {
const ruleConfig = this.rules.get(rule.type);
if (!ruleConfig) {
throw new Error(`未知的驗證規(guī)則: ${rule.type}`);
}
if (!ruleConfig.pattern.test(value)) {
return rule.message || ruleConfig.message;
}
return true;
}
// 處理長度驗證
if (rule.minLength !== undefined || rule.maxLength !== undefined) {
const length = value.length;
if (rule.minLength !== undefined && length < rule.minLength) {
return rule.message || `長度不能少于${rule.minLength}個字符`;
}
if (rule.maxLength !== undefined && length > rule.maxLength) {
return rule.message || `長度不能超過${rule.maxLength}個字符`;
}
return true;
}
// 處理范圍驗證
if (rule.min !== undefined || rule.max !== undefined) {
const num = Number(value);
if (isNaN(num)) {
return '請輸入有效的數(shù)字';
}
if (rule.min !== undefined && num < rule.min) {
return rule.message || `數(shù)值不能小于${rule.min}`;
}
if (rule.max !== undefined && num > rule.max) {
return rule.message || `數(shù)值不能大于${rule.max}`;
}
return true;
}
}
throw new Error(`不支持的驗證規(guī)則格式`);
}
/**
* 驗證整個表單
*/
validateForm(formData, schema) {
const results = {};
let isValid = true;
for (const [field, rules] of Object.entries(schema)) {
const value = formData[field];
const result = this.validate(value, rules);
results[field] = result;
if (!result.valid) {
isValid = false;
}
}
return {
valid: isValid,
fields: results
};
}
}
// 使用示例
const validator = new FormValidator();
// 添加自定義規(guī)則
validator.addRule('username', /^[a-zA-Z][a-zA-Z0-9_]{5,17}$/, '用戶名必須以字母開頭,6-18位,只能包含字母、數(shù)字、下劃線');
// 添加自定義驗證器
validator.addValidator('passwordMatch', (value, formData) => {
return value === formData.password ? true : '兩次輸入的密碼不一致';
});
// 驗證單個值
console.log(validator.validate('user@example.com', 'email'));
console.log(validator.validate('13800138000', 'phone'));
// 驗證整個表單
const formData = {
username: 'john_doe',
email: 'john@example.com',
phone: '13800138000',
password: 'Password123!',
confirmPassword: 'Password123!',
age: 25
};
const schema = {
username: ['required', 'username'],
email: ['required', 'email'],
phone: ['required', 'phone'],
password: [
'required',
{ pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, message: '密碼必須包含大小寫字母、數(shù)字和特殊字符,至少8位' }
],
confirmPassword: [
'required',
{ validator: (value, data) => value === data.password ? true : '兩次輸入的密碼不一致' }
],
age: [
'required',
{ min: 18, max: 100, message: '年齡必須在18-100歲之間' }
]
};
console.log(validator.validateForm(formData, schema));
7. 性能測試與對比
讓我們對我們的驗證庫進行性能測試:
/**
* 性能測試
*/
function performanceTest() {
const validator = new FormValidator();
const iterations = 10000;
// 測試數(shù)據(jù)
const testData = {
valid_email: 'user@example.com',
invalid_email: 'invalid.email',
valid_phone: '13800138000',
invalid_phone: '12345678901',
valid_url: 'https://www.example.com',
invalid_url: 'not-a-url'
};
console.log(`開始性能測試,迭代次數(shù): ${iterations}`);
console.log('='.repeat(50));
// 郵箱驗證性能測試
console.time('郵箱驗證 (valid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.valid_email, 'email');
}
console.timeEnd('郵箱驗證 (valid)');
console.time('郵箱驗證 (invalid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.invalid_email, 'email');
}
console.timeEnd('郵箱驗證 (invalid)');
// 手機號驗證性能測試
console.time('手機號驗證 (valid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.valid_phone, 'phone');
}
console.timeEnd('手機號驗證 (valid)');
console.time('手機號驗證 (invalid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.invalid_phone, 'phone');
}
console.timeEnd('手機號驗證 (invalid)');
// URL驗證性能測試
console.time('URL驗證 (valid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.valid_url, 'url');
}
console.timeEnd('URL驗證 (valid)');
console.time('URL驗證 (invalid)');
for (let i = 0; i < iterations; i++) {
validator.validate(testData.invalid_url, 'url');
}
console.timeEnd('URL驗證 (invalid)');
}
// 運行性能測試
performanceTest();
8. 最佳實踐總結(jié)
8.1 正則表達式編寫原則
- 簡單明了:盡量使用簡單的表達式,避免過度復(fù)雜
- 性能優(yōu)先:考慮回溯和性能影響
- 可讀性:添加注釋,使用命名捕獲組
- 測試覆蓋:為每個正則表達式編寫測試用例
- 錯誤處理:提供友好的錯誤提示
8.2 前端驗證策略
- 客戶端驗證:提供即時反饋,改善用戶體驗
- 服務(wù)端驗證:確保數(shù)據(jù)安全和完整性
- 漸進增強:從基礎(chǔ)驗證開始,逐步增加復(fù)雜度
- 可訪問性:確保驗證錯誤對屏幕閱讀器友好
8.3 調(diào)試技巧
- 使用在線工具:如 regex101.com、RegExr 等
- 逐步構(gòu)建:從簡單模式開始,逐步添加復(fù)雜度
- 單元測試:為每個正則表達式編寫測試
- 性能分析:使用性能測試工具識別瓶頸
結(jié)語
正則表達式是前端開發(fā)中的強大工具,掌握它能夠顯著提升開發(fā)效率和代碼質(zhì)量。本文涵蓋了從基礎(chǔ)到進階的各個方面,包括表單驗證、字符串處理、性能優(yōu)化等實戰(zhàn)場景。
記住,正則表達式的學(xué)習(xí)是一個循序漸進的過程。建議從簡單的模式開始,逐步增加復(fù)雜度,并在實際項目中不斷練習(xí)和應(yīng)用。同時,也要注意性能影響,避免過度復(fù)雜的表達式。
希望這篇文章能夠幫助你在前端開發(fā)中更好地運用正則表達式,解決實際開發(fā)中遇到的各種字符串處理問題。持續(xù)學(xué)習(xí)和實踐,你會發(fā)現(xiàn)正則表達式變得越來越簡單和直觀。
參考資料
- MDN Regular Expressions
- JavaScript Regular Expression Tutorial
- RegExr: Learn, Build, & Test RegEx
- Regex101: Online Regex Tester
到此這篇關(guān)于前端正則表達式表單驗證與字符串處理高頻場景的文章就介紹到這了,更多相關(guān)前端正則表達式表單驗證與字符串處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript下一個還原h(huán)tml代碼的正則
javascript下一個還原h(huán)tml代碼的正則...2007-08-08
JScript中正則表達函數(shù)的說明與應(yīng)用
JScript中正則表達函數(shù)的說明與應(yīng)用...2007-04-04

