正则表达式完全指南(基础语法 + 实战应用 + 性能优化 + 最佳实践)

摘要:本文从实际开发场景出发,系统讲解正则表达式的使用方法。通过大量实战案例、对比表格和代码示例,帮助读者快速掌握正则表达式的核心用法,能够立即应用到表单验证、数据提取、文本处理等实际工作中。

关键词:正则表达式、RegExp、表单验证、字符串处理、JavaScript、文本匹配、模式匹配、前端开发

适合人群:前端开发工程师、后端开发工程师、全栈开发者、计算机专业学生、需要处理文本数据的开发者

阅读时间:约 40 分钟

版本信息:JavaScript ES6+ | 兼容 Node.js 14+ / 现代浏览器


文章目录

  • [一、快速入门:5 分钟学会正则](#一、快速入门:5 分钟学会正则)
    • [1.1 最简单的例子](#1.1 最简单的例子)
    • [1.2 第一个实战:验证邮箱](#1.2 第一个实战:验证邮箱)
    • [1.3 核心概念一图懂](#1.3 核心概念一图懂)
  • 二、基础语法:从零开始学
    • [2.1 字符类:匹配什么类型的字符](#2.1 字符类:匹配什么类型的字符)
    • [2.2 量词:匹配多少次](#2.2 量词:匹配多少次)
    • [2.3 边界:在哪里匹配](#2.3 边界:在哪里匹配)
    • [2.4 分组:组合多个模式](#2.4 分组:组合多个模式)
  • [三、JavaScript 中的使用方法](#三、JavaScript 中的使用方法)
    • [3.1 如何创建正则](#3.1 如何创建正则)
    • [3.2 6 个常用方法](#3.2 6 个常用方法)
    • [3.3 标志:控制匹配行为](#3.3 标志:控制匹配行为)
  • 四、实战场景:拿来就能用
    • [4.1 表单验证(5 个常用场景)](#4.1 表单验证(5 个常用场景))
    • [4.2 数据提取(3 个常用场景)](#4.2 数据提取(3 个常用场景))
    • [4.3 文本替换(3 个常用场景)](#4.3 文本替换(3 个常用场景))
  • [五、完整项目:3 个实战工具类](#五、完整项目:3 个实战工具类)
    • [实战 1:表单验证工具类](#实战 1:表单验证工具类)
    • [实战 2:文本处理工具类](#实战 2:文本处理工具类)
    • [实战 3:日志解析器](#实战 3:日志解析器)
  • 六、性能优化:避免踩坑
    • [6.1 灾难性回溯](#6.1 灾难性回溯)
    • [6.2 预编译优化](#6.2 预编译优化)
    • [6.3 非捕获组优化](#6.3 非捕获组优化)
  • 七、常见陷阱与避坑指南
    • [7.1 贪婪匹配 vs 惰性匹配](#7.1 贪婪匹配 vs 惰性匹配)
    • [7.2 特殊字符转义](#7.2 特殊字符转义)
    • [7.3 全局标志状态问题](#7.3 全局标志状态问题)
  • [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
  • [📝 学习资源与建议](#📝 学习资源与建议)
  • [📚 参考资料](#📚 参考资料)

一、快速入门:5 分钟学会正则

1.1 最简单的例子

  正则表达式听起来很复杂,但其实它就是一个**"查找模式"的工具**。让我们从最简单的例子开始:

场景 1:检查字符串中是否包含 "hello"

javascript 复制代码
// 不用正则(只能精确匹配)
const str = 'Hello World';
str.includes('hello'); // false(大小写不匹配)

// 使用正则(可以忽略大小写)
const regex = /hello/i;  // /i 表示忽略大小写
regex.test('Hello World'); // true ✅
regex.test('hello world'); // true ✅
regex.test('HELLO WORLD'); // true ✅

场景 2:检查是否是纯数字

javascript 复制代码
// 不用正则(需要写很多代码)
function isAllNumbers(str) {
  for (let char of str) {
    if (char < '0' || char > '9') return false;
  }
  return true;
}

// 使用正则(一行代码搞定)
const regex = /^\d+$/;  // ^开头 \d数字 +一次或多次 $结尾
regex.test('12345');  // true ✅
regex.test('123abc'); // false ❌

  看到没有?正则表达式能让复杂的文本处理变得非常简单

1.2 第一个实战:验证邮箱

  这是开发中最常见的场景之一。让我们一步步构建邮箱验证的正则:

第 1 步:分析邮箱格式

复制代码
user@example.com
├── 用户名:字母、数字、点、下划线等
├── @ 符号
├── 域名:字母、数字、点、连字符
└── 顶级域名:至少 2 个字母(.com, .org, .cn)

第 2 步:逐步构建正则

javascript 复制代码
// 第 1 版:最简单的版本(能匹配,但不严格)
const v1 = /.+@.+\..+/;

// 第 2 版:限制字符类型(更严格)
const v2 = /[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+/;

// 第 3 版:生产环境版本(推荐)
const v3 = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

第 3 步:测试验证

javascript 复制代码
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

// ✅ 正确的邮箱
emailRegex.test('user@example.com');      // true
emailRegex.test('user.name@example.com'); // true
emailRegex.test('user+tag@example.com');  // true

// ❌ 错误的邮箱
emailRegex.test('invalid-email');         // false(没有 @)
emailRegex.test('@example.com');          // false(没有用户名)
emailRegex.test('user@');                 // false(没有域名)

  这就是正则表达式的核心用法:定义一个模式,然后用来测试或提取文本!

1.3 核心概念一图懂

  正则表达式只有4 个核心概念,掌握了就能应对 90% 的场景:

概念 作用 类比 示例
字符类 匹配什么类型的字符 "我要找数字" \d 匹配数字
量词 匹配多少次 "我要找 3 个数字" \d{3} 匹配 3 个数字
边界 在哪里匹配 "必须从开头开始" ^\d{3} 从开头匹配 3 个数字
分组 组合多个模式 "我要找 (3 个数字)-(4 个字母)" (\d{3})-([a-z]{4})

  记住这 4 个概念,你就能读懂大部分正则表达式了!

💡 学习建议 :不要试图一次记住所有语法。先掌握最常用的符号(\d+^$),其他用到时再查表。
📚 在线测试工具Regex101 - 可以实时测试正则,强烈推荐!


二、基础语法:从零开始学

2.1 字符类:匹配什么类型的字符

  字符类决定了"我要找什么类型的字符"。这是最基础的概念。

最常用的 7 个字符类

符号 含义 记忆口诀 示例 匹配结果
\d 数字(0-9) digit(数字) \d "1", "2", "9"
\D 非数字 大写表示"非" \D "a", "#", " "
\w 单词字符 word(单词) \w "a", "1", "_"
\W 非单词字符 大写表示"非" \W "@", "#", " "
\s 空白字符 space(空格) \s " ", "\t", "\n"
\S 非空白字符 大写表示"非" \S "a", "1", "#"
. 任意字符 一个点代表"任意" . "a", "1", "@"(除换行符)

实战示例:

javascript 复制代码
// 匹配 3 个数字
const regex1 = /\d\d\d/;
regex1.test('abc123def'); // true(找到 "123")

// 匹配单词字符
const regex2 = /\w+/;
regex2.test('hello'); // true(找到 "hello")

// 匹配任意 3 个字符
const regex3 = /.../;
regex3.test('abc'); // true(找到 "abc")

自定义字符类(方括号)

符号 含义 示例 匹配结果
[abc] 匹配 a、b 或 c [abc]at "aat", "bat", "cat"
[^abc] 匹配 a、b、c 的字符 [^abc]at "dat", "eat"
[a-z] 匹配小写字母 [a-z]+ "hello", "world"
[A-Z] 匹配大写字母 [A-Z]+ "HELLO", "WORLD"
[0-9] 匹配数字(等价于 \d [0-9]+ "123", "456"
[a-zA-Z] 匹配所有字母 [a-zA-Z]+ "Hello", "World"

💡 使用场景 :当你需要匹配特定范围的字符时,使用方括号。比如 [0-9] 匹配数字,[a-zA-Z] 匹配字母。

2.2 量词:匹配多少次

  量词决定了"前面的字符要出现多少次"。

最常用的 6 个量词

符号 含义 记忆口诀 示例 匹配结果
* 0 次或多次 "有没有都行,有再多也要" ab*c "ac", "abc", "abbc"
+ 1 次或多次 "至少要有 1 个" ab+c "abc", "abbc"(不匹配 "ac")
? 0 次或 1 次 "可有可无,最多一个" ab?c "ac", "abc"
{n} 恰好 n 次 "必须是 n 个" \d{3} "123", "456"
{n,} 至少 n 次 "至少 n 个,越多越好" \d{2,} "12", "123", "1234"
{n,m} n 到 m 次 "在 n 到 m 之间" \d{2,4} "12", "123", "1234"

实战示例:

javascript 复制代码
// 匹配手机号(11 位数字)
const phoneRegex = /^1\d{10}$/;
phoneRegex.test('13812345678'); // true ✅
phoneRegex.test('1381234567');  // false ❌(只有 10 位)

// 匹配密码(至少 8 位)
const passwordRegex = /^.{8,}$/;
passwordRegex.test('12345678'); // true ✅
passwordRegex.test('1234567');  // false ❌(只有 7 位)

// 匹配年份(4 位数字)
const yearRegex = /^\d{4}$/;
yearRegex.test('2024'); // true ✅
yearRegex.test('24');   // false ❌

2.3 边界:在哪里匹配

  边界决定了"从哪里开始匹配,到哪里结束"。

最常用的 4 个边界符

符号 含义 记忆口诀 示例 匹配结果
^ 字符串开头 "从开头开始" ^abc 匹配 "abc123",不匹配 "123abc"
$ 字符串结尾 "到结尾结束" abc$ 匹配 "123abc",不匹配 "abc123"
\b 单词边界 "单词的分界线" \bword\b 匹配 "hello word",不匹配 "sword"
\B 非单词边界 "单词内部" \Bword 匹配 "sword",不匹配 "word"

实战示例:

javascript 复制代码
// 验证:必须是纯数字(不能包含其他字符)
const regex1 = /^\d+$/;
regex1.test('12345');  // true ✅
regex1.test('123abc'); // false ❌(包含字母)

// 验证:必须以 "http" 开头
const regex2 = /^http/;
regex2.test('https://example.com'); // true ✅
regex2.test('ftp://example.com');   // false ❌

// 验证:必须以 ".com" 结尾
const regex3 = /\.com$/;
regex3.test('example.com'); // true ✅
regex3.test('example.org'); // false ❌

💡 重要提示^$ 是表单验证的必备符号!它们确保整个字符串都符合模式,而不是部分匹配。

2.4 分组:组合多个模式

  分组用于将多个字符组合成一个单元,可以对其应用量词或提取内容。

最常用的 4 种分组

符号 名称 是否捕获 示例 说明
() 捕获分组 ✅ 是 (abc)+ 匹配 "abc", "abcabc",并捕获内容
(?:) 非捕获分组 ❌ 否 (?:abc)+ 匹配但不捕获,性能更好
(?<name>) 命名捕获分组 ✅ 是 (?<year>\d{4}) 使用名称引用,可读性高
` ` - `cat

实战示例:

javascript 复制代码
// 捕获分组:提取区号和号码
const phoneRegex = /^(\d{3})-(\d{4})$/;
const match = '010-1234'.match(phoneRegex);
console.log(match[1]); // "010"(区号)
console.log(match[2]); // "1234"(号码)

// 命名捕获分组(更清晰)
const phoneRegex2 = /^(?<area>\d{3})-(?<number>\d{4})$/;
const match2 = '010-1234'.match(phoneRegex2);
console.log(match2.groups.area);   // "010"
console.log(match2.groups.number); // "1234"

// 或操作:匹配多种情况
const protocolRegex = /^(http|https|ftp):\/\//;
protocolRegex.test('http://example.com');  // true ✅
protocolRegex.test('https://example.com'); // true ✅
protocolRegex.test('ftp://example.com');   // true ✅

三、JavaScript 中的使用方法

3.1 如何创建正则

  JavaScript 提供两种创建方式,90% 的场景用第一种就够了:

方式 1:字面量(推荐,90% 场景使用)

javascript 复制代码
// 语法:/模式/标志
const regex = /hello/i;

// 优点:简洁、性能好、易读
// 适用:模式固定的场景

方式 2:构造函数(动态生成时使用)

javascript 复制代码
// 语法:new RegExp('模式', '标志')
const pattern = 'hello';
const regex = new RegExp(pattern, 'i');

// 优点:可以动态生成模式
// 适用:模式来自变量或用户输入

实战对比:

javascript 复制代码
// ✅ 场景 1:模式固定 → 用字面量
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

// ✅ 场景 2:模式来自变量 → 用构造函数
const username = 'john';
const usernameRegex = new RegExp(`^${username}$`, 'i');

3.2 6 个常用方法

  JavaScript 提供了多种使用正则的方法,但最常用的只有 6 个

方法 1:test() - 测试是否匹配(最常用)

javascript 复制代码
const regex = /hello/i;

regex.test('Hello World'); // true ✅
regex.test('Goodbye');     // false ❌

// 🎯 使用场景:表单验证、条件判断
if (/^\d+$/.test(input)) {
  console.log('输入是纯数字');
}

方法 2:match() - 提取匹配内容

javascript 复制代码
const str = 'My phone is 13812345678';
const regex = /1\d{10}/;

const result = str.match(regex);
console.log(result[0]); // "13812345678"

// 🎯 使用场景:从文本中提取数据

方法 3:matchAll() - 提取所有匹配

javascript 复制代码
const str = '价格:100元,折扣:20元,总计:80元';
const regex = /\d+/g;  // 注意:必须加 g 标志

const results = [...str.matchAll(regex)];
console.log(results.map(m => m[0])); // ["100", "20", "80"]

// 🎯 使用场景:提取所有数字、所有邮箱等

方法 4:replace() - 替换内容

javascript 复制代码
const str = 'hello world';
const regex = /hello/;

const result = str.replace(regex, 'hi');
console.log(result); // "hi world"

// 🎯 使用场景:文本替换、格式化

方法 5:search() - 查找位置

javascript 复制代码
const str = 'hello world';
const regex = /world/;

const position = str.search(regex);
console.log(position); // 6(从索引 6 开始)

// 🎯 使用场景:定位文本位置

方法 6:split() - 分割字符串

javascript 复制代码
const str = 'apple,banana,orange';
const regex = /,/;

const result = str.split(regex);
console.log(result); // ["apple", "banana", "orange"]

// 🎯 使用场景:分割文本

方法选择指南

你的需求 使用方法 返回值
检查是否匹配 test() true / false
提取第一个匹配 match() 数组或 null
提取所有匹配 matchAll() 迭代器
替换内容 replace() 新字符串
查找位置 search() 数字索引
分割文本 split() 数组

3.3 标志:控制匹配行为

  标志用于修改正则的匹配行为,最常用的只有 3 个:

标志 名称 作用 示例 说明
g global 全局匹配(找所有) /a/g 找到所有 "a",不是第一个
i ignoreCase 忽略大小写 /a/i 匹配 "a" 和 "A"
m multiline 多行模式 /^a/m ^ 匹配每行开头

实战示例:

javascript 复制代码
// g 标志:找到所有匹配
const str = 'hello hello hello';
console.log(str.match(/hello/g)); // ["hello", "hello", "hello"]

// i 标志:忽略大小写
const regex = /hello/i;
console.log(regex.test('HELLO')); // true ✅

// gi 组合:全局 + 忽略大小写
console.log('Hello HELLO hello'.match(/hello/gi)); 
// ["Hello", "HELLO", "hello"]

// m 标志:多行模式
const text = 'Line 1\nLine 2\nLine 3';
console.log(text.match(/^Line/gm)); // ["Line", "Line", "Line"]

💡 开发建议 :90% 的场景只需要 gi 两个标志。其他标志用到时再查。


四、实战场景:拿来就能用

4.1 表单验证(5 个常用场景)

  这是正则最常见的应用场景。以下是 5 个最常用的验证正则,直接复制就能用

场景 1:邮箱验证

javascript 复制代码
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

// 测试
emailRegex.test('user@example.com');      // true ✅
emailRegex.test('user.name@example.com'); // true ✅
emailRegex.test('invalid-email');         // false ❌

场景 2:手机号验证(中国大陆)

javascript 复制代码
const phoneRegex = /^1[3-9]\d{9}$/;

// 测试
phoneRegex.test('13812345678'); // true ✅(移动)
phoneRegex.test('13012345678'); // true ✅(联通)
phoneRegex.test('13312345678'); // true ✅(电信)
phoneRegex.test('12345678901'); // false ❌(号段错误)

场景 3:密码强度验证

javascript 复制代码
// 中等强度:至少 8 位,包含字母和数字
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;

// 测试
passwordRegex.test('Password123'); // true ✅
passwordRegex.test('12345678');    // false ❌(缺少字母)
passwordRegex.test('Password');    // false ❌(缺少数字)

场景 4:用户名验证

javascript 复制代码
// 4-16 位,只能包含字母、数字、下划线
const usernameRegex = /^[a-zA-Z0-9_]{4,16}$/;

// 测试
usernameRegex.test('user_123');    // true ✅
usernameRegex.test('ab');          // false ❌(太短)
usernameRegex.test('user@123');    // false ❌(包含特殊字符)

场景 5:URL 验证

javascript 复制代码
const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;

// 测试
urlRegex.test('https://www.example.com'); // true ✅
urlRegex.test('http://example.com/path'); // true ✅
urlRegex.test('example.com');             // true ✅(无协议)
urlRegex.test('not-a-url');               // false ❌

表单验证速查表

验证类型 正则表达式 说明
邮箱 /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ 标准邮箱格式
手机号 /^1[3-9]\d{9}$/ 中国大陆 11 位
密码(中) /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/ 8 位 + 字母 + 数字
用户名 /^[a-zA-Z0-9_]{4,16}$/ 4-16 位字母数字下划线
URL /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ http/https 协议

4.2 数据提取(3 个常用场景)

场景 1:提取所有数字

javascript 复制代码
const text = '价格:100元,折扣:20元,总计:80元';
const numbers = text.match(/\d+/g);

console.log(numbers); // ["100", "20", "80"]
console.log(numbers.map(Number)); // [100, 20, 80](转为数字)

场景 2:提取所有邮箱

javascript 复制代码
const text = '联系邮箱:user@example.com 或 admin@test.org';
const emails = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g);

console.log(emails); // ["user@example.com", "admin@test.org"]

场景 3:提取 HTML 标签内容

javascript 复制代码
const html = '<h1>Title</h1><p>Content</p>';

// 方法 1:提取所有标签
const tags = html.match(/<[^>]+>/g);
console.log(tags); // ["<h1>", "</h1>", "<p>", "</p>"]

// 方法 2:提取标签和内容
const regex = /<([^>]+)>(.*?)<\/\1>/g;
let match;
while ((match = regex.exec(html)) !== null) {
  console.log(`标签: ${match[1]}, 内容: ${match[2]}`);
}
// 输出:
// 标签: h1, 内容: Title
// 标签: p, 内容: Content

数据提取速查表

提取内容 正则表达式 说明
所有数字 /\d+/g 匹配连续的数字
所有邮箱 /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g 匹配邮箱格式
HTML 标签 /<[^>]+>/g 匹配 <...> 格式
URL /https?:\/\/[^\s]+/g 匹配 http/https 链接
中文 /[\u4e00-\u9fa5]+/g 匹配中文字符

4.3 文本替换(3 个常用场景)

场景 1:驼峰转下划线

javascript 复制代码
function camelToSnake(str) {
  return str.replace(/([A-Z])/g, '_$1').toLowerCase();
}

console.log(camelToSnake('camelCaseString')); // "camel_case_string"
console.log(camelToSnake('userName'));        // "user_name"

场景 2:下划线转驼峰

javascript 复制代码
function snakeToCamel(str) {
  return str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
}

console.log(snakeToCamel('camel_case_string')); // "camelCaseString"
console.log(snakeToCamel('user_name'));         // "userName"

场景 3:移除 HTML 标签

javascript 复制代码
function stripHtml(html) {
  return html.replace(/<[^>]+>/g, '');
}

console.log(stripHtml('<h1>Title</h1><p>Content</p>')); // "TitleContent"

文本替换速查表

替换场景 正则表达式 替换为 说明
驼峰转下划线 /([A-Z])/g _$1 大写字母前加下划线
下划线转驼峰 /_([a-z])/g 大写字母 下划线后字母转大写
移除 HTML 标签 /<[^>]+>/g '' 删除所有标签
移除多余空格 /\s+/g ' ' 多个空格变一个
脱敏手机号 /(\d{3})\d{4}(\d{4})/ $1****$2 中间 4 位变星号

五、完整项目:3 个实战工具类

实战 1:表单验证工具类

javascript 复制代码
/**
 * 表单验证工具类
 * 使用示例:
 * FormValidator.isEmail('user@example.com') // true
 * FormValidator.isPhone('13812345678')      // true
 */
class FormValidator {
  // 邮箱验证
  static isEmail(email) {
    const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    return regex.test(email);
  }

  // 手机号验证
  static isPhone(phone) {
    const regex = /^1[3-9]\d{9}$/;
    return regex.test(phone);
  }

  // 密码强度验证(中等)
  static isPasswordValid(password) {
    const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
    return regex.test(password);
  }

  // 用户名验证(4-16 位,字母数字下划线)
  static isUsernameValid(username) {
    const regex = /^[a-zA-Z0-9_]{4,16}$/;
    return regex.test(username);
  }

  // URL 验证
  static isURL(url) {
    const regex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
    return regex.test(url);
  }

  // 身份证验证(18 位)
  static isIDCard(id) {
    const regex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
    return regex.test(id);
  }
}

// 使用示例
console.log(FormValidator.isEmail('user@example.com')); // true
console.log(FormValidator.isPhone('13812345678'));      // true
console.log(FormValidator.isPasswordValid('Pass1234')); // true

项目知识点

  • 静态方法(class 语法)
  • 正则表达式封装
  • 工具类设计模式

实战 2:文本处理工具类

javascript 复制代码
/**
 * 文本处理工具类
 * 使用示例:
 * TextProcessor.extractNumbers('价格:100元') // [100]
 * TextProcessor.camelToSnake('userName')      // "user_name"
 */
class TextProcessor {
  // 提取所有数字
  static extractNumbers(text) {
    const regex = /\d+/g;
    const matches = text.match(regex);
    return matches ? matches.map(Number) : [];
  }

  // 提取所有邮箱
  static extractEmails(text) {
    const regex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
    const matches = text.match(regex);
    return matches || [];
  }

  // 驼峰转下划线
  static camelToSnake(str) {
    return str.replace(/([A-Z])/g, '_$1').toLowerCase();
  }

  // 下划线转驼峰
  static snakeToCamel(str) {
    return str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
  }

  // 移除 HTML 标签
  static stripHtml(html) {
    return html.replace(/<[^>]+>/g, '');
  }

  // 手机号脱敏
  static maskPhone(phone) {
    return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
  }
}

// 使用示例
const text = '价格:100元,折扣:20元,总计:80元';
console.log(TextProcessor.extractNumbers(text)); // [100, 20, 80]

console.log(TextProcessor.camelToSnake('camelCaseString')); // 'camel_case_string'
console.log(TextProcessor.snakeToCamel('camel_case_string')); // 'camelCaseString'

const html = '<h1>Title</h1><p>Content</p>';
console.log(TextProcessor.stripHtml(html)); // 'TitleContent'

console.log(TextProcessor.maskPhone('13812345678')); // '138****5678'

项目知识点

  • 全局匹配(g 标志)
  • 捕获组与替换
  • 回调函数在 replace() 中的应用

实战 3:日志解析器

javascript 复制代码
/**
 * 日志解析器
 * 使用示例:
 * LogParser.parseApacheLog(logLine) // 返回解析后的对象
 */
class LogParser {
  // Apache 日志格式解析
  static parseApacheLog(logLine) {
    const regex = /^(\S+) (\S+) (\S+) \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+)$/;
    const match = logLine.match(regex);
    
    if (!match) return null;
    
    return {
      ip: match[1],
      ident: match[2],
      user: match[3],
      timestamp: match[4],
      method: match[5],
      path: match[6],
      protocol: match[7],
      status: parseInt(match[8]),
      size: parseInt(match[9])
    };
  }

  // 使用命名捕获组(ES2018+,更清晰)
  static parseApacheLogModern(logLine) {
    const regex = /^(?<ip>\S+) (?<ident>\S+) (?<user>\S+) \[(?<timestamp>[^\]]+)\] "(?<method>\S+) (?<path>\S+) (?<protocol>\S+)" (?<status>\d{3}) (?<size>\d+)$/;
    const match = logLine.match(regex);
    
    return match ? match.groups : null;
  }
}

// 使用示例
const logLine = '192.168.1.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326';

console.log(LogParser.parseApacheLog(logLine));
// {
//   ip: '192.168.1.1',
//   ident: '-',
//   user: 'frank',
//   timestamp: '10/Oct/2000:13:55:36 -0700',
//   method: 'GET',
//   path: '/apache_pb.gif',
//   protocol: 'HTTP/1.0',
//   status: 200,
//   size: 2326
// }

项目知识点

  • 复杂正则表达式编写
  • 捕获组数据提取
  • 命名捕获组(ES2018+)
  • 数据类型转换

六、性能优化:避免踩坑

6.1 灾难性回溯

  灾难性回溯是正则表达式最常见的性能问题,会导致程序卡死。

什么是灾难性回溯?

javascript 复制代码
// ❌ 危险:嵌套量词导致指数级回溯
const badRegex = /^(a+)+$/;
badRegex.test('a'.repeat(100) + 'b'); // 可能卡死!

// ✅ 安全:避免嵌套量词
const goodRegex = /^a+$/;
goodRegex.test('a'.repeat(100) + 'b'); // 瞬间返回 false

如何避免?

危险写法 安全写法 原因
(a+)+ a+ 避免嵌套量词
(\w+)* \w* 避免嵌套量词
(.*).* .* 避免重复匹配

6.2 预编译优化

  在循环中重复创建正则表达式会严重影响性能。

javascript 复制代码
// ❌ 性能差:在循环中创建正则
function badApproach(strings) {
  const results = [];
  for (const str of strings) {
    const regex = new RegExp('pattern'); // 每次循环都创建
    results.push(regex.test(str));
  }
  return results;
}

// ✅ 性能好:预编译正则
function goodApproach(strings) {
  const regex = /pattern/; // 只创建一次
  const results = [];
  for (const str of strings) {
    results.push(regex.test(str));
  }
  return results;
}

6.3 非捕获组优化

  如果不需要引用分组内容,使用非捕获组可以提升性能。

javascript 复制代码
// ❌ 使用捕获组(不需要引用时浪费内存)
const regex1 = /(https?:\/\/)?(www\.)?example\.com/;

// ✅ 使用非捕获组(推荐)
const regex2 = /(?:https?:\/\/)?(?:www\.)?example\.com/;

// 测试结果相同
console.log(regex1.test('https://www.example.com')); // true
console.log(regex2.test('https://www.example.com')); // true

七、常见陷阱与避坑指南

7.1 贪婪匹配 vs 惰性匹配

  贪婪匹配会尽可能多地匹配字符,而惰性匹配会尽可能少地匹配。

javascript 复制代码
const html = '<h1>Title</h1><p>Content</p>';

// ❌ 贪婪匹配(匹配整个字符串)
const greedy = /<.*>/;
console.log(html.match(greedy)[0]); 
// '<h1>Title</h1><p>Content</p>'

// ✅ 惰性匹配(只匹配第一个标签)
const lazy = /<.*?>/;
console.log(html.match(lazy)[0]); 
// '<h1>'

// ✅ 更精确的匹配(推荐)
const precise = /<[^>]+>/;
console.log(html.match(precise)[0]); 
// '<h1>'

贪婪 vs 惰性对比表

类型 语法 行为 适用场景
贪婪 .* 匹配尽可能多 匹配整个内容
惰性 .*? 匹配尽可能少 匹配第一个出现的内容
精确 [^>]+ 匹配特定字符 匹配 HTML 标签(推荐)

7.2 特殊字符转义

  正则表达式中有多个特殊字符,需要转义才能匹配字面量。

需要转义的特殊字符

字符 含义 转义后 示例
. 任意字符 \. find\.example\.com
* 0 次或多次 \* 100\*
+ 1 次或多次 \+ c\+\+
? 0 次或 1 次 \? what\?
^ 开头 \^ 10\^
$ 结尾 \$ 100\$
{} 量词 \{\} \{1,3\}
[] 字符类 \[\] \[\]
() 分组 \(\) \(group\)
` ` `
\ 转义符 \\ path\\to
/ 分隔符 \/ http:\/\/

转义工具函数

javascript 复制代码
// 自动转义特殊字符
function escapeRegex(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// 使用示例
const domain = 'example.com';
const regex = new RegExp(escapeRegex(domain));
console.log(regex.test('example.com')); // true
console.log(regex.test('exampleXcom')); // false

7.3 全局标志状态问题

  使用 g 标志的正则表达式会记住上次匹配的位置,可能导致意外行为。

javascript 复制代码
// ⚠️ 问题:全局标志会记住 lastIndex
const regex = /hello/g;
const str = 'hello world hello';

console.log(regex.test(str)); // true
console.log(regex.lastIndex); // 5

console.log(regex.test(str)); // true
console.log(regex.lastIndex); // 17

console.log(regex.test(str)); // false(没有更多匹配了)

// ✅ 解决方案 1:每次创建新实例
console.log(/hello/g.test(str)); // true
console.log(/hello/g.test(str)); // true

// ✅ 解决方案 2:手动重置 lastIndex
regex.lastIndex = 0;
console.log(regex.test(str)); // true

💡 开发建议 :如果只需要测试是否匹配,不要使用 g 标志,或者每次都创建新的正则表达式。


❓ 常见问题 FAQ

Q1:正则表达式性能很差吗?

  A:正则表达式的性能取决于编写方式。简单的正则性能非常好,但需要注意以下几点:

  - 避免灾难性回溯(嵌套量词)

  - 预编译正则表达式(不要在循环中创建)

  - 使用非捕获组减少内存占用

  - 简单场景优先使用字符串方法(如 includes()

Q2:如何调试复杂的正则表达式?

  A:推荐使用以下工具和方法:

  - Regex101https://regex101.com/):在线测试,支持解释和调试

  - 分步构建 :先写简单版本,逐步添加复杂度

  - 添加注释 :使用 x 标志(部分语言支持)或分行写

  - 单元测试:编写多个测试用例验证边界情况

Q3:正向先行断言 (?=) 是什么意思?

  A:正向先行断言用于检查某个位置后面是否跟着特定模式,但不消耗字符。例如:

  - (?=.*[A-Z]):检查后面是否包含大写字母

  - (?=.*\d):检查后面是否包含数字

  - 常用于密码验证等需要同时满足多个条件的场景

Q4:如何在正则中使用变量?

  A:使用 RegExp 构造函数和模板字符串:

javascript 复制代码
const username = 'john';
const regex = new RegExp(`^${username}$`, 'i');
// 注意:如果变量包含特殊字符,需要先转义

Q5:\d[0-9] 有什么区别?

  A:在 JavaScript 中,两者基本等价,都匹配 ASCII 数字 0-9。但在某些语言(如 Python、Java)中,\d 可能匹配 Unicode 数字(如阿拉伯数字)。如果只需要匹配 ASCII 数字,建议使用 [0-9] 更明确。

Q6:如何处理多行文本?

  A:使用 m(多行)标志:

javascript 复制代码
const text = 'Line 1\nLine 2\nLine 3';
const regex = /^Line/gm;
console.log(text.match(regex)); // ['Line', 'Line', 'Line']

  m 标志让 ^$ 匹配每行的开头和结尾,而不是整个字符串的开头和结尾。

Q7:正则表达式可以嵌套使用吗?

  A:正则表达式本身不支持递归嵌套(部分语言如 PCRE 支持 (?R) 递归)。对于嵌套结构(如 HTML、JSON),建议使用专门的解析库,而不是正则表达式。


📝 学习资源与建议

学习建议

  1. 循序渐进 :先掌握基础语法(字符类、量词),再学习高级特性(分组、断言)

  2. 多练习 :使用在线工具(Regex101)练习各种场景

  3. 理解原理 :了解正则引擎的工作原理,避免性能陷阱

  4. 适度使用 :简单场景优先使用字符串方法,复杂场景再用正则

  5. 编写测试:为正则表达式编写单元测试,确保覆盖边界情况

官方资源

推荐书籍

  • 《正则表达式必知必会》- Ben Forta
  • 《精通正则表达式》- Jeffrey E.F. Friedl
  • 《JavaScript 高级程序设计》- 正则表达式章节

📚 参考资料

相关推荐
AbandonForce1 小时前
简谈线程池
开发语言·c++·算法
李少兄1 小时前
JavaScript 运算符完全指南
java·开发语言·javascript
广州灵眸科技有限公司1 小时前
瑞芯微(EASY EAI)RV1126B display
开发语言·数据库·人工智能·科技·嵌入式硬件
Huangxy__1 小时前
java+ai 全栈项目
java·开发语言
Nil2081 小时前
leetcode 79单词搜索
开发语言·c#
Blockchina1 小时前
AI Agent 写权限安全吗?Codex、Claude Code 与 GitHub Copilot 最小权限实战指南
开发语言·php
ctlover1 小时前
网络编程和多线程
开发语言·网络·python
神仙别闹1 小时前
基于C#实现(WinForm)文件管理系统
java·开发语言·c#
Mr_liu_6661 小时前
ns3-gym例子解析_基础例子与wifi例子_DQN(3)
开发语言·c++·python·dqn·ns3