游乐游手机版
首页/编程语言/文章详情

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

时间:2026-06-15 06:46
针对前端表单验证,本文系统介绍了正则表达式在邮箱、手机号、身份证、密码强度、网址及IP地址等场景中的验证方法,并提供了不同严格级别的实用函数与测试用例,便于开发者直接使用,提升效率。

1. 正则表达式基础回顾

1.1 基本概念

正则表达式,说白了就是一种用来匹配字符串中字符组合的模式。在Ja vaScript里,创建它的方式有两种:字面量和构造函数。哪种顺手就用哪种,差别不大。

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

// 字面量方式
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 邮箱验证

邮箱验证大概是前端开发中最常见的需求,没有之一。一个靠谱的邮箱正则,得能应付各种千奇百怪的格式。下面给了三种版本:基础版、严格版(能过RFC 5322大部分规则)、实用版(平衡了准确性和性能)。实际项目里推荐直接上实用版。

/**
 * 邮箱验证 - 基础版本
 * 适合大多数场景
 */
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 手机号验证

不同国家号码格式不一样,这里以中国手机号为例。需要注意:最新的号段已经覆盖到19x了,上面的正则/^1[3-9]d{9}$/已经包含了。另外还提供了带区号(+86)和固话号码的验证。

/**
 * 中国手机号验证
 * 支持最新的号段
 */
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 身份证号验证

中国大陆身份证号有严格的校验规则——不仅格式要对,最后一位校验码也要算出来核对。下面的函数同时支持15位和18位身份证,18位还做了校验码验证,可以说是相当全面了。

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

密码强度验证通常需要组合多个条件。下面给了基础版(至少8位+字母+数字)、中级版(大小写字母/数字/特殊字符至少三种)、高级版(可自定义配置规则,灵活度最高)。实际项目里可以根据安全等级要求选一个。

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

/**
 * 密码强度验证 - 中级版本
 * 必须包含大小写字母、数字、特殊字符中的至少3种
 */
function validatePasswordMedium(password) {
    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]{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格式五花八门,基础版覆盖了http/https和大部分常见格式。完整版还支持了ftp协议和localhost。另外附了一个提取域名的函数,很实用。

/**
 * 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('https://example.com/path/to/page')); // true
console.log(extractDomain('https://www.example.com/path')); // www.example.com

2.6 IP地址验证

IP验证分IPv4和IPv6,这里分别给出了函数,最后还有一个通用的validateIP,一次搞定两种。

/**
 * 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标签。下面提供了四种常用操作:移除所有标签、移除指定标签、仅保留指定标签、转义特殊字符(防XSS必备)。

/**
 * 移除所有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 = {
        '&': '&',
        '<': '<',
        '>': '>',
        '"': '"',
        "'": '''
    };
    
    return html.replace(/[&<>"']/g, (match) => escapeMap[match]);
}

// 测试用例
const htmlContent = '

Hello World!

'; console.log(removeAllHTMLTags(htmlContent)); // "Hello World!" console.log(removeSpecificHTMLTags(htmlContent, ['script'])); // "

Hello World!

" console.log(keepSpecificHTMLTags(htmlContent, ['p'])); // "

Hello World!

"

3.2 特殊字符过滤

用户输入经常需要过滤掉不需要的字符。这里提供了移除非字母数字、非中文、非ASCII、自定义过滤和移除非打印字符等实用函数。

/**
 * 移除非字母数字字符
 */
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-x1Fx7F-x9F]/g, '');
}

// 测试用例
console.log(removeNonAlphanumeric('Hello, 世界! 123')); // "Hello123"
console.log(removeNonChinese('Hello, 世界! 123')); // "世界"
console.log(filterCustomCharacters('Hello123!@#', 'a-zA-Z')); // "Hello"

3.3 数字格式化

数字格式化在金融、电商页面几乎天天碰到。千位分隔符、货币格式化、提取数字、科学计数法转普通数字,一个都不能少。

/**
 * 千位分隔符格式化
 */
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 字符串提取

从复杂文本里挖出有用信息,是正则的强项。下面四个函数覆盖了最常见的提取需求:URL参数、CSS属性、JSON字符串、Markdown链接。

/**
 * 提取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 文本替换

文本替换是正则的核心玩法之一。这里实现了智能大小写转换(camelCase、PascalCase、kebab-case、snake_case)、电话号码格式化、敏感词过滤和日期格式化。每个都很实用,尤其是大小写转换,在编写命名规范时能省不少事。

/**
 * 智能大小写转换
 */
function smartCase(str, type = 'camel') {
    switch (type) {
        case 'camel':
            return str.replace(/(?:^w|[A-Z]|bw)/g, (word, index) => {
                return index === 0 ? word.toLowerCase() : word.toUpperCase();
            }).replace(/s+/g, '');
        
        case 'pascal':
            return str.replace(/(?:^w|[A-Z]|bw)/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 避免回溯灾难

/**
 * 性能对比:贪婪 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 预编译正则表达式

如果同一个正则要被反复使用,预编译并缓存起来能省下不少构造时间。

/**
 * 预编译正则表达式缓存
 */
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: {
        // Ja vaScript 不直接支持,但可以通过字符类模拟
        pattern: /a[^a]*b/,
    },
};

5. 常见错误与调试方法

5.1 常见错误

正则写多了,踩坑是难免的。下面列出的这几个错误,几乎每个开发者都碰到过:忘记转义特殊字符、使用了错误的字符类、贪婪匹配导致匹配范围过大、忘记全局标志、边界匹配不精确。

/**
 * 常见错误示例
 */
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 调试工具和方法

/**
 * 正则表达式调试工具
 */
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. 实战项目:表单验证库

好,学到这里,可以综合运用上面所有知识点来造一个完整的表单验证库。这个FormValidator类内置了邮箱、手机、身份证、URL、数字、整数、中文、英文、邮编等常用规则,还支持自定义规则和自定义验证器,甚至可以一次性验证整个表单。看代码就知道,复用性和扩展性都很强。

/**
 * 表单验证库
 */
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-zd@$!%*?&]{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. 性能测试与对比

最后,验证一下这个库的性能。下面跑了一万次迭代,分别测了邮箱、手机号、URL的有效和无效输入。

/**
 * 性能测试
 */
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. 可读性:适当加注释,用命名捕获组(虽然JS还不太完善,但思路要有)。
  4. 测试覆盖:每个正则都应该有对应的测试用例,边界情况多想想。
  5. 错误处理:给用户友好的错误提示,别直接扔一长串正则字符串。

8.2 前端验证策略

  1. 客户端验证:即时反馈,用户体验更流畅。
  2. 服务端验证:永远别信任前端传过来的数据,必须双重校验。
  3. 渐进增强:从基础验证开始,逐步加复杂度,别一上来就想写完美正则。
  4. 可访问性:验证错误提示要对屏幕阅读器友好,让所有用户都能用。

8.3 调试技巧

  1. 使用在线工具:regex101.com、RegExr 这些工具能帮你可视化匹配过程,非常直观。
  2. 逐步构建:从最简单的模式开始,逐步加条件,边加边测。
  3. 单元测试:为每个正则写测试,回归有保障。
  4. 性能分析:用性能测试工具找瓶颈,别等上线了才发现卡顿。

结语

正则表达式确实是前端开发中的一把利器,掌握好了能大幅提升开发效率和代码质量。这篇文章从基础概念讲起,一路覆盖了表单验证、字符串处理、性能优化等实战场景,并且最后还组装了一个完整的验证库。希望这个学习路径和代码示例能给正在啃正则的你一些实在的帮助。

记住,学习正则没有捷径,但也不需要刻意死记。从简单模式开始,多练多用,慢慢就会形成肌肉记忆。同时要始终留个心眼——性能很重要,别让一个复杂的正则把你的页面拖垮。

持续实践,你会发现正则越来越顺手,甚至能帮你解决很多原本需要写循环才能搞定的字符串处理难题。

参考资料

  • MDN Regular Expressions
  • Ja vaScript Regular Expression Tutorial
  • RegExr: Learn, Build, & Test RegEx
  • Regex101: Online Regex Tester
来源:https://www.jb51.net/program/359228r2x.htm
上一篇各编程语言正则表达式标准支持综合对比 下一篇PHP四种主流Token实现方案详解
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
AWS RDS 数据库配置入门与基础操作指南
编程语言 · 2026-07-10

AWS RDS 数据库配置入门与基础操作指南

本文介绍了AWSRDS的基本概念与核心价值,即提供托管式关系数据库服务,简化运维。详细阐述了创建RDS实例的关键配置步骤,包括引擎选择、实例规格、存储与网络设置。最后,指导读者如何通过多种方式安全连接至数据库实例,并开始进行数据操作,为后续应用开发奠定基础。

PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案
编程语言 · 2026-07-10

PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案

PHPMVC中AJAX请求返回整页HTML的常见原因是控制器方法未正确输出响应或未终止执行,导致框架渲染视图。解决方法是在控制器中设置JSON响应头、输出数据后调用exit()明确终止,同时前端使用小写url和dataType: "json "。

Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南
编程语言 · 2026-07-10

Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南

手动构造RSA公钥时,模数N为*big Int类型,不能直接使用超长十进制字面量,需通过SetString或UnmarshalText方法解析字符串。公钥指数E可直接赋值,推荐65537。生产环境应使用rsa GenerateKey生成密钥对,避免手动构造引发的安全和格式错误。

Go语言实现HTTP定时轮询监控多URL响应时间与状态检测
编程语言 · 2026-07-10

Go语言实现HTTP定时轮询监控多URL响应时间与状态检测

使用Go语言实现HTTP定时轮询监控,通过按行分割与Tab解析URL列表,避免闭包陷阱和nil指针,每个URL启动独立ticker安全并发请求,并配置超时控制与资源关闭,确保响应时间与状态码准确检测。

Tkinter中Label标签在主循环动态更新的正确方法
编程语言 · 2026-07-10

Tkinter中Label标签在主循环动态更新的正确方法

在Tkinter中正确动态更新标签的方法:将标签组件的textvariable参数绑定到一个StringVar变量,然后通过调用该变量的 set()方法更新其值,界面会自动刷新。这样避免直接修改text属性或调用update()。此做法实现数据与界面的解耦,代码更简洁,响应更及时,避免手动同步的闪烁,推荐做法。