前端正则表达式实战合集:表单验证与字符串处理高频场景

前端正则表达式实战合集:表单验证与字符串处理高频场景

文章目录

  • 前端正则表达式实战合集:表单验证与字符串处理高频场景
    • [1. 正则表达式基础回顾](#1. 正则表达式基础回顾)
      • [1.1 基本概念](#1.1 基本概念)
      • [1.2 常用元字符](#1.2 常用元字符)
    • [2. 表单验证场景实战](#2. 表单验证场景实战)
      • [2.1 邮箱验证](#2.1 邮箱验证)
      • [2.2 手机号验证](#2.2 手机号验证)
      • [2.3 身份证号验证](#2.3 身份证号验证)
      • [2.4 密码强度验证](#2.4 密码强度验证)
      • [2.5 URL验证](#2.5 URL验证)
      • [2.6 IP地址验证](#2.6 IP地址验证)
    • [3. 字符串处理高频场景](#3. 字符串处理高频场景)
      • [3.1 HTML标签清理](#3.1 HTML标签清理)
      • [3.2 特殊字符过滤](#3.2 特殊字符过滤)
      • [3.3 数字格式化](#3.3 数字格式化)
      • [3.4 字符串提取](#3.4 字符串提取)
      • [3.5 文本替换](#3.5 文本替换)
    • [4. 性能优化技巧](#4. 性能优化技巧)
      • [4.1 避免回溯灾难](#4.1 避免回溯灾难)
      • [4.2 预编译正则表达式](#4.2 预编译正则表达式)
      • [4.3 使用合适的量词](#4.3 使用合适的量词)
    • [5. 常见错误与调试方法](#5. 常见错误与调试方法)
      • [5.1 常见错误](#5.1 常见错误)
      • [5.2 调试工具和方法](#5.2 调试工具和方法)
    • [6. 实战项目:表单验证库](#6. 实战项目:表单验证库)
    • [7. 性能测试与对比](#7. 性能测试与对比)
    • [8. 最佳实践总结](#8. 最佳实践总结)
      • [8.1 正则表达式编写原则](#8.1 正则表达式编写原则)
      • [8.2 前端验证策略](#8.2 前端验证策略)
      • [8.3 调试技巧](#8.3 调试技巧)
    • 结语
    • 参考资料

正则表达式是前端开发中不可或缺的工具,掌握它能让你的表单验证和字符串处理工作事半功倍。本文将带你深入实战,从基础到进阶,全面掌握前端开发中的正则表达式应用。

1. 正则表达式基础回顾

1.1 基本概念

正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在JavaScript中,我们可以使用两种方式创建正则表达式:

javascript 复制代码
// 字面量方式
const regex1 = /abc/;

// 构造函数方式
const regex2 = new RegExp('abc');

1.2 常用元字符

元字符 描述 示例
. 匹配除换行符外的任意字符 /a.c/ 匹配 "abc", "aac"
^ 匹配字符串的开始 /^abc/ 匹配以abc开头的字符串
$ 匹配字符串的结束 /abc$/ 匹配以abc结尾的字符串
* 匹配前面的子表达式零次或多次 /ab*c/ 匹配 "ac", "abc", "abbc"
+ 匹配前面的子表达式一次或多次 /ab+c/ 匹配 "abc", "abbc"
? 匹配前面的子表达式零次或一次 /ab?c/ 匹配 "ac", "abc"
[] 字符集合,匹配其中任意一个字符 /[abc]/ 匹配 "a", "b", "c"
\d 匹配数字,等价于[0-9] /\d+/ 匹配一个或多个数字
\w 匹配字母、数字、下划线 /\w+/ 匹配单词字符
\s 匹配空白字符 /\s+/ 匹配一个或多个空白

2. 表单验证场景实战

2.1 邮箱验证

邮箱验证是前端开发中最常见的需求之一。一个完善的邮箱正则应该考虑各种格式:

javascript 复制代码
/**
 * 邮箱验证 - 基础版本
 * 适合大多数场景
 */
function validateEmailBasic(email) {
    const regex = /^[\w.-]+@[\w.-]+\.\w+$/;
    return regex.test(email);
}

/**
 * 邮箱验证 - 严格版本
 * 符合RFC 5322标准的大部分规则
 */
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 手机号验证

不同国家的手机号格式不同,这里以中国手机号为例:

javascript 复制代码
/**
 * 中国手机号验证
 * 支持最新的号段
 */
function validateChinesePhone(phone) {
    const regex = /^1[3-9]\d{9}$/;
    return regex.test(phone);
}

/**
 * 带区号的手机号验证
 */
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 身份证号验证

中国大陆的身份证号有严格的校验规则:

javascript 复制代码
/**
 * 身份证号验证
 * 包含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 密码强度验证

密码强度验证通常需要满足多个条件:

javascript 复制代码
/**
 * 密码强度验证 - 基础版本
 */
function validatePasswordBasic(password) {
    // 至少8位,包含字母和数字
    const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/;
    return regex.test(password);
}

/**
 * 密码强度验证 - 中级版本
 * 必须包含大小写字母、数字、特殊字符中的至少3种
 */
function validatePasswordMedium(password) {
    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
    return regex.test(password);
}

/**
 * 密码强度验证 - 高级版本
 * 自定义规则,可配置不同强度等级
 */
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格式验证需要考虑多种协议和格式:

javascript 复制代码
/**
 * URL验证 - 基础版本
 */
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验证 - 完整版本
 * 支持更多协议和格式
 */
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:

javascript 复制代码
/**
 * 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标签清理

在处理富文本内容时,经常需要清理HTML标签:

javascript 复制代码
/**
 * 移除所有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 : '';
    });
}

/**
 * 转义HTML特殊字符
 */
function escapeHTML(html) {
    const escapeMap = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;'
    };
    
    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 特殊字符过滤

处理用户输入时,经常需要过滤特殊字符:

javascript 复制代码
/**
 * 移除非字母数字字符
 */
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 数字格式化

数字格式化在金融、电商等场景中非常常见:

javascript 复制代码
/**
 * 千位分隔符格式化
 */
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}`;
}

/**
 * 提取字符串中的数字
 */
function extractNumbers(str) {
    const matches = str.match(/\d+(?:\.\d+)?/g);
    return matches ? matches.map(Number) : [];
}

/**
 * 科学计数法转普通数字
 */
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元,优惠了20%')); // [123.45, 20]
console.log(scientificToDecimal('1.23e-3')); // "0.00123"

3.4 字符串提取

从复杂文本中提取有用信息是正则表达式的强项:

javascript 复制代码
/**
 * 提取URL参数
 */
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 文本替换

文本替换是正则表达式的核心功能之一:

javascript 复制代码
/**
 * 智能大小写转换
 */
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. 性能优化技巧

正则表达式的性能对大型应用至关重要:

4.1 避免回溯灾难

javascript 复制代码
/**
 * 性能对比:贪婪 vs 懒惰匹配
 */
function comparePerformance() {
    const testString = 'a'.repeat(10000) + 'b';
    
    // 贪婪匹配 - 可能导致性能问题
    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 预编译正则表达式

javascript 复制代码
/**
 * 预编译正则表达式缓存
 */
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 使用合适的量词

javascript 复制代码
/**
 * 选择合适的量词
 */
const optimizations = {
    // 使用具体的量词而不是通用量词
    specific: {
        phone: /^\d{11}$/, // 而不是 /^\d+$/
        postalCode: /^\d{6}$/, // 而不是 /^\d+$/
    },
    
    // 使用原子组减少回溯
    atomic: {
        // 使用 (?>...) 原子组
        pattern: /(?>a+)b/,
    },
    
    // 使用 possessive 量词(在某些正则引擎中)
    possessive: {
        // JavaScript 不直接支持,但可以通过字符类模拟
        pattern: /a[^a]*b/,
    }
};

5. 常见错误与调试方法

5.1 常见错误

javascript 复制代码
/**
 * 常见错误示例
 */
const commonMistakes = {
    // 1. 忘记转义特殊字符
    unescapedDot: /www.example.com/, // 应该为 /www\.example\.com/
    
    // 2. 使用错误的字符类
    wrongCharClass: /[a-zA-Z0-9_]/, // 应该使用 /\w/
    
    // 3. 贪婪匹配导致的问题
    greedyProblem: /<.*>/, // 会匹配整个字符串,应该使用 /<.*?>/
    
    // 4. 忘记全局标志
    noGlobalFlag: 'hello world'.replace(/o/, '0'), // 只替换第一个
    
    // 5. 错误的边界匹配
    wrongBoundary: /word/, // 会匹配 "password" 中的 "word"
};

5.2 调试工具和方法

javascript 复制代码
/**
 * 正则表达式调试工具
 */
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('匹配结果:', 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('无匹配结果');
        }
    }
    
    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. 实战项目:表单验证库

让我们综合运用所学知识,创建一个完整的表单验证库:

javascript 复制代码
/**
 * 表单验证库
 */
class FormValidator {
    constructor() {
        this.rules = new Map();
        this.customValidators = new Map();
        this.setupDefaultRules();
    }
    
    setupDefaultRules() {
        // 内置验证规则
        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: '请输入有效的数字'
        });
        
        this.rules.set('integer', {
            pattern: /^\d+$/,
            message: '请输入有效的整数'
        });
        
        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: '请输入有效的邮政编码'
        });
    }
    
    /**
     * 添加自定义验证规则
     */
    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('验证器必须是函数');
        }
        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
        };
    }
    
    /**
     * 验证单个规则
     */
    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;
        }
        
        // 处理内置规则
        if (typeof rule === 'string') {
            const ruleConfig = this.rules.get(rule);
            if (!ruleConfig) {
                throw new Error(`未知的验证规则: ${rule}`);
            }
            
            if (!ruleConfig.pattern.test(value)) {
                return ruleConfig.message;
            }
            return true;
        }
        
        // 处理正则表达式
        if (rule instanceof RegExp) {
            if (!rule.test(value)) {
                return '格式不正确';
            }
            return true;
        }
        
        // 处理对象形式的规则
        if (typeof rule === 'object') {
            // 处理正则表达式规则
            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;
            }
            
            // 处理内置规则
            if (rule.type) {
                const ruleConfig = this.rules.get(rule.type);
                if (!ruleConfig) {
                    throw new Error(`未知的验证规则: ${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 '请输入有效的数字';
                }
                
                if (rule.min !== undefined && num < rule.min) {
                    return rule.message || `数值不能小于${rule.min}`;
                }
                
                if (rule.max !== undefined && num > rule.max) {
                    return rule.message || `数值不能大于${rule.max}`;
                }
                return true;
            }
        }
        
        throw new Error(`不支持的验证规则格式`);
    }
    
    /**
     * 验证整个表单
     */
    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();

// 添加自定义规则
validator.addRule('username', /^[a-zA-Z][a-zA-Z0-9_]{5,17}$/, '用户名必须以字母开头,6-18位,只能包含字母、数字、下划线');

// 添加自定义验证器
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: '密码必须包含大小写字母、数字和特殊字符,至少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. 性能测试与对比

让我们对我们的验证库进行性能测试:

javascript 复制代码
/**
 * 性能测试
 */
function performanceTest() {
    const validator = new FormValidator();
    const iterations = 10000;
    
    // 测试数据
    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(`开始性能测试,迭代次数: ${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. 最佳实践总结

8.1 正则表达式编写原则

  1. 简单明了:尽量使用简单的表达式,避免过度复杂
  2. 性能优先:考虑回溯和性能影响
  3. 可读性:添加注释,使用命名捕获组
  4. 测试覆盖:为每个正则表达式编写测试用例
  5. 错误处理:提供友好的错误提示

8.2 前端验证策略

  1. 客户端验证:提供即时反馈,改善用户体验
  2. 服务端验证:确保数据安全和完整性
  3. 渐进增强:从基础验证开始,逐步增加复杂度
  4. 可访问性:确保验证错误对屏幕阅读器友好

8.3 调试技巧

  1. 使用在线工具:如 regex101.com、RegExr 等
  2. 逐步构建:从简单模式开始,逐步添加复杂度
  3. 单元测试:为每个正则表达式编写测试
  4. 性能分析:使用性能测试工具识别瓶颈

结语

正则表达式是前端开发中的强大工具,掌握它能够显著提升开发效率和代码质量。本文涵盖了从基础到进阶的各个方面,包括表单验证、字符串处理、性能优化等实战场景。

记住,正则表达式的学习是一个循序渐进的过程。建议从简单的模式开始,逐步增加复杂度,并在实际项目中不断练习和应用。同时,也要注意性能影响,避免过度复杂的表达式。

希望这篇文章能够帮助你在前端开发中更好地运用正则表达式,解决实际开发中遇到的各种字符串处理问题。持续学习和实践,你会发现正则表达式变得越来越简单和直观。

参考资料


相关推荐
baozj2 小时前
🚀 手动改 500 个文件?不存在的!我用 AST 撸了个 Vue 国际化神器
前端·javascript·vue.js
用户4099322502122 小时前
为什么Vue 3的计算属性能解决模板臃肿、性能优化和双向同步三大痛点?
前端·ai编程·trae
海云前端12 小时前
Vue首屏加速秘籍 组件按需加载真能省一半时间
前端
蛋仔聊测试2 小时前
Playwright 中route 方法模拟测试数据(Mocking)详解
前端·python·测试
零号机2 小时前
使用TRAE 30分钟极速开发一款划词中英互译浏览器插件
前端·人工智能
疯狂踩坑人3 小时前
结合400行mini-react代码,图文解说React原理
前端·react.js·面试
Mintopia3 小时前
🚀 共绩算力:3分钟拥有自己的文生图AI服务-容器化部署 StableDiffusion1.5-WebUI 应用
前端·人工智能·aigc
街尾杂货店&3 小时前
CSS - transition 过渡属性及使用方法(示例代码)
前端·css
CH_X_M3 小时前
为什么在AI对话中选择用sse而不是web socket?
前端