摘要:本文从实际开发场景出发,系统讲解正则表达式的使用方法。通过大量实战案例、对比表格和代码示例,帮助读者快速掌握正则表达式的核心用法,能够立即应用到表单验证、数据提取、文本处理等实际工作中。
关键词:正则表达式、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% 的场景只需要
g和i两个标志。其他标志用到时再查。
四、实战场景:拿来就能用
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:推荐使用以下工具和方法:
- Regex101 (https://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. 编写测试:为正则表达式编写单元测试,确保覆盖边界情况
官方资源
- MDN - 正则表达式
- ECMAScript 规范 - RegExp
- Regex101 - 在线正则测试工具
- RegExr - 交互式正则学习工具
- Debuggex - 可视化正则表达式
推荐书籍
- 《正则表达式必知必会》- Ben Forta
- 《精通正则表达式》- Jeffrey E.F. Friedl
- 《JavaScript 高级程序设计》- 正则表达式章节