本文将系统讲解正则表达式的语法规则和实战技巧,通过大量实例帮助你掌握这一强大的文本处理工具,提升开发效率。
📋 目录
一、正则表达式基础
1.1 什么是正则表达式
正则表达式(Regular Expression)是一种描述字符串模式的语言,用于:
- 🔍 搜索:查找匹配的文本
- ✅ 验证:检查格式是否正确
- 🔄 替换:批量修改文本
- 📤 提取:从文本中提取信息
1.2 基本语法
javascript
// 创建正则表达式
const regex1 = /pattern/flags; // 字面量
const regex2 = new RegExp('pattern', 'flags'); // 构造函数
// 常用标志(flags)
/pattern/g // global: 全局匹配
/pattern/i // ignoreCase: 忽略大小写
/pattern/m // multiline: 多行模式
/pattern/s // dotAll: .匹配换行符
/pattern/u // unicode: Unicode模式
/pattern/y // sticky: 粘性匹配
// 组合使用
/pattern/gi // 全局+忽略大小写
1.3 普通字符与特殊字符
javascript
// 普通字符:直接匹配
/hello/ // 匹配 "hello"
/abc123/ // 匹配 "abc123"
// 特殊字符需要转义
/\./ // 匹配 "."
/\*/ // 匹配 "*"
/\?/ // 匹配 "?"
/\\/ // 匹配 "\"
/\// // 匹配 "/"
// 需要转义的特殊字符
// . * + ? ^ $ { } [ ] ( ) | \ /
二、元字符与量词
2.1 字符类
javascript
// 预定义字符类
\d // 数字 [0-9]
\D // 非数字 [^0-9]
\w // 单词字符 [a-zA-Z0-9_]
\W // 非单词字符 [^a-zA-Z0-9_]
\s // 空白字符 [ \t\n\r\f\v]
\S // 非空白字符
. // 任意字符(除换行符)
// 示例
/\d{3}/.test('123') // true
/\w+/.test('hello_123') // true
/\s/.test(' ') // true
2.2 自定义字符类
javascript
// 字符集合 []
[abc] // 匹配 a 或 b 或 c
[a-z] // 匹配 a-z 任意字母
[A-Z] // 匹配 A-Z 任意字母
[0-9] // 匹配 0-9 任意数字
[a-zA-Z] // 匹配任意字母
[a-zA-Z0-9]// 匹配字母或数字
// 否定字符集 [^]
[^abc] // 匹配除 a、b、c 外的字符
[^0-9] // 匹配非数字
// 示例
/[aeiou]/.test('hello') // true (匹配元音)
/[^aeiou]/.test('hello') // true (匹配辅音)
2.3 量词
javascript
// 基本量词
* // 0次或多次
+ // 1次或多次
? // 0次或1次
{n} // 恰好n次
{n,} // 至少n次
{n,m} // n到m次
// 示例
/a*/.test('') // true (0次)
/a+/.test('aaa') // true (多次)
/a?/.test('b') // true (0次)
/a{3}/.test('aaa') // true (恰好3次)
/a{2,4}/.test('aaa') // true (2-4次)
2.4 贪婪与非贪婪
javascript
// 贪婪模式(默认):尽可能多匹配
const str = '<div>content</div>';
str.match(/<.*>/); // ['<div>content</div>']
// 非贪婪模式:尽可能少匹配(加?)
str.match(/<.*?>/); // ['<div>']
// 非贪婪量词
*? // 0次或多次(非贪婪)
+? // 1次或多次(非贪婪)
?? // 0次或1次(非贪婪)
{n,m}? // n到m次(非贪婪)
// 实际应用
const html = '<p>段落1</p><p>段落2</p>';
html.match(/<p>.*<\/p>/g); // ['<p>段落1</p><p>段落2</p>']
html.match(/<p>.*?<\/p>/g); // ['<p>段落1</p>', '<p>段落2</p>']
三、分组与引用
3.1 捕获分组
javascript
// 基本分组 ()
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = '2024-01-15'.match(regex);
// match[0] = '2024-01-15' (完整匹配)
// match[1] = '2024' (第1组)
// match[2] = '01' (第2组)
// match[3] = '15' (第3组)
// 命名分组 (?<name>)
const namedRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const namedMatch = '2024-01-15'.match(namedRegex);
// namedMatch.groups.year = '2024'
// namedMatch.groups.month = '01'
// namedMatch.groups.day = '15'
3.2 非捕获分组
javascript
// 非捕获分组 (?:)
// 只分组不捕获,提高性能
const regex = /(?:https?:\/\/)?www\.(\w+)\.com/;
const match = 'https://www.example.com'.match(regex);
// match[1] = 'example' (只有一个捕获组)
// 对比
/(\d+)-(\d+)/.exec('123-456'); // ['123-456', '123', '456']
/(?:\d+)-(\d+)/.exec('123-456'); // ['123-456', '456']
3.3 反向引用
javascript
// 引用前面的捕获组
// \1 引用第1组,\2 引用第2组...
// 匹配重复单词
const regex = /\b(\w+)\s+\1\b/gi;
'hello hello world'.match(regex); // ['hello hello']
// 匹配成对标签
const tagRegex = /<(\w+)>.*?<\/\1>/;
'<div>content</div>'.match(tagRegex); // ['<div>content</div>', 'div']
// 命名引用 \k<name>
const namedRef = /(?<word>\w+)\s+\k<word>/;
'hello hello'.match(namedRef); // ['hello hello', 'hello']
3.4 替换中的引用
javascript
// $1, $2... 引用捕获组
const str = '2024-01-15';
str.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
// '01/15/2024'
// 命名引用 $<name>
str.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, '$<m>/$<d>/$<y>');
// '01/15/2024'
// 特殊替换模式
// $& - 匹配的子串
// $` - 匹配前的文本
// $' - 匹配后的文本
// $$ - 字面量$
'hello'.replace(/l/g, '[$&]'); // 'he[l][l]o'
四、断言与边界
4.1 边界匹配
javascript
// 行首行尾
^ // 行首
$ // 行尾
/^hello/.test('hello world') // true
/world$/.test('hello world') // true
/^hello$/.test('hello') // true (完全匹配)
// 单词边界
\b // 单词边界
\B // 非单词边界
/\bcat\b/.test('cat') // true
/\bcat\b/.test('category') // false
/\Bcat\B/.test('location') // true
4.2 先行断言
javascript
// 正向先行断言 (?=)
// 匹配后面是xxx的位置
/\d+(?=元)/.exec('100元') // ['100']
/\d+(?=元)/.exec('100美元') // null
// 负向先行断言 (?!)
// 匹配后面不是xxx的位置
/\d+(?!元)/.exec('100美元') // ['100']
/\d+(?!元)/.exec('100元') // ['10'] (贪婪匹配到10)
// 实际应用:密码验证
// 必须包含数字和字母
/^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/
4.3 后行断言
javascript
// 正向后行断言 (?<=)
// 匹配前面是xxx的位置
/(?<=\$)\d+/.exec('$100') // ['100']
/(?<=¥)\d+/.exec('¥200') // ['200']
// 负向后行断言 (?<!)
// 匹配前面不是xxx的位置
/(?<!\$)\d+/.exec('€100') // ['100']
// 组合使用
// 提取引号内的内容
/(?<=")[^"]+(?=")/.exec('"hello"') // ['hello']
4.4 断言实战
javascript
// 千分位格式化
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
formatNumber(1234567); // '1,234,567'
// 密码强度验证
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
// 至少8位,包含大小写字母、数字、特殊字符
// 提取URL参数
const url = 'https://example.com?name=test&age=18';
const params = {};
url.replace(/[?&]([^=]+)=([^&]*)/g, (_, key, value) => {
params[key] = value;
});
// { name: 'test', age: '18' }
五、JavaScript正则API
5.1 RegExp方法
javascript
// test() - 测试是否匹配
/\d+/.test('abc123'); // true
// exec() - 执行匹配,返回详细信息
const regex = /(\d+)/g;
let match;
while ((match = regex.exec('a1b2c3')) !== null) {
console.log(match[0], match.index);
}
// '1' 1
// '2' 3
// '3' 5
// 注意:exec配合g标志会记住lastIndex
const re = /a/g;
re.exec('aaa'); // index: 0
re.exec('aaa'); // index: 1
re.exec('aaa'); // index: 2
re.exec('aaa'); // null
5.2 String方法
javascript
// match() - 返回匹配结果
'hello123world456'.match(/\d+/); // ['123']
'hello123world456'.match(/\d+/g); // ['123', '456']
// matchAll() - 返回迭代器(ES2020)
const matches = 'a1b2c3'.matchAll(/(\w)(\d)/g);
for (const match of matches) {
console.log(match);
}
// ['a1', 'a', '1', index: 0]
// ['b2', 'b', '2', index: 2]
// ['c3', 'c', '3', index: 4]
// search() - 返回首次匹配位置
'hello world'.search(/world/); // 6
'hello world'.search(/xyz/); // -1
// replace() - 替换
'hello'.replace(/l/g, 'L'); // 'heLLo'
// 函数替换
'hello'.replace(/./g, (char, index) => {
return index % 2 === 0 ? char.toUpperCase() : char;
}); // 'HeLlO'
// split() - 分割
'a1b2c3'.split(/\d/); // ['a', 'b', 'c', '']
'a1b2c3'.split(/(\d)/); // ['a', '1', 'b', '2', 'c', '3', '']
5.3 常用技巧
javascript
// 1. 动态构建正则
const keyword = 'hello';
const regex = new RegExp(keyword, 'gi');
// 转义特殊字符
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 2. 获取所有匹配及分组
function getAllMatches(str, regex) {
return [...str.matchAll(regex)].map(match => ({
full: match[0],
groups: match.slice(1),
index: match.index
}));
}
// 3. 替换回调函数
'hello world'.replace(/\b\w/g, char => char.toUpperCase());
// 'Hello World'
// 4. 条件替换
const text = 'cat dog cat';
text.replace(/cat|dog/g, match => {
return match === 'cat' ? '🐱' : '🐶';
}); // '🐱 🐶 🐱'
六、实战案例大全
6.1 表单验证
javascript
// 手机号验证(中国大陆)
const phoneRegex = /^1[3-9]\d{9}$/;
phoneRegex.test('13812345678'); // true
// 邮箱验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
emailRegex.test('test@example.com'); // true
// 身份证验证(简单版)
const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
// URL验证
const urlRegex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/;
// IP地址验证
const ipRegex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
// 密码强度(8-20位,包含大小写字母和数字)
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;
// 中文验证
const chineseRegex = /^[\u4e00-\u9fa5]+$/;
chineseRegex.test('你好'); // true
// 用户名(字母开头,允许字母数字下划线,4-16位)
const usernameRegex = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/;
6.2 文本处理
javascript
// 去除HTML标签
function stripHtml(html) {
return html.replace(/<[^>]*>/g, '');
}
stripHtml('<p>Hello <b>World</b></p>'); // 'Hello World'
// 提取所有链接
function extractLinks(html) {
const regex = /href=["']([^"']+)["']/g;
return [...html.matchAll(regex)].map(m => m[1]);
}
// 高亮关键词
function highlight(text, keyword) {
const regex = new RegExp(`(${escapeRegExp(keyword)})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
// 驼峰转下划线
function camelToSnake(str) {
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}
camelToSnake('getUserName'); // 'get_user_name'
// 下划线转驼峰
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
snakeToCamel('get_user_name'); // 'getUserName'
// 首字母大写
function capitalize(str) {
return str.replace(/\b\w/g, char => char.toUpperCase());
}
capitalize('hello world'); // 'Hello World'
6.3 数据提取
javascript
// 提取日期
function extractDates(text) {
const regex = /\d{4}[-/]\d{1,2}[-/]\d{1,2}/g;
return text.match(regex) || [];
}
extractDates('日期:2024-01-15 和 2024/2/20');
// ['2024-01-15', '2024/2/20']
// 提取金额
function extractMoney(text) {
const regex = /[¥$€]\s*[\d,]+\.?\d*/g;
return text.match(regex) || [];
}
extractMoney('价格:¥199.00 或 $29.99');
// ['¥199.00', '$29.99']
// 提取手机号
function extractPhones(text) {
const regex = /1[3-9]\d{9}/g;
return text.match(regex) || [];
}
// 提取邮箱
function extractEmails(text) {
const regex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
return text.match(regex) || [];
}
// 解析查询字符串
function parseQueryString(url) {
const params = {};
const regex = /[?&]([^=]+)=([^&]*)/g;
let match;
while ((match = regex.exec(url)) !== null) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
}
parseQueryString('?name=test&age=18');
// { name: 'test', age: '18' }
6.4 格式化处理
javascript
// 千分位格式化
function formatThousands(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
formatThousands(1234567.89); // '1,234,567.89'
// 手机号脱敏
function maskPhone(phone) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
maskPhone('13812345678'); // '138****5678'
// 邮箱脱敏
function maskEmail(email) {
return email.replace(/(.{2}).*(@.*)/, '$1***$2');
}
maskEmail('test@example.com'); // 'te***@example.com'
// 身份证脱敏
function maskIdCard(id) {
return id.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
}
maskIdCard('110101199001011234'); // '110101********1234'
// 银行卡格式化
function formatBankCard(card) {
return card.replace(/(\d{4})(?=\d)/g, '$1 ');
}
formatBankCard('6222021234567890123');
// '6222 0212 3456 7890 123'
// 清理多余空格
function cleanSpaces(str) {
return str.replace(/\s+/g, ' ').trim();
}
cleanSpaces(' hello world '); // 'hello world'
6.5 代码处理
javascript
// 提取注释
function extractComments(code) {
const singleLine = code.match(/\/\/.*$/gm) || [];
const multiLine = code.match(/\/\*[\s\S]*?\*\//g) || [];
return [...singleLine, ...multiLine];
}
// 移除注释
function removeComments(code) {
return code
.replace(/\/\/.*$/gm, '')
.replace(/\/\*[\s\S]*?\*\//g, '');
}
// 提取函数名
function extractFunctionNames(code) {
const regex = /function\s+(\w+)|(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>|(\w+)\s*:\s*(?:async\s*)?function/g;
const names = [];
let match;
while ((match = regex.exec(code)) !== null) {
names.push(match[1] || match[2] || match[3]);
}
return names.filter(Boolean);
}
// 提取import语句
function extractImports(code) {
const regex = /import\s+(?:{[^}]+}|\w+)\s+from\s+['"]([^'"]+)['"]/g;
return [...code.matchAll(regex)].map(m => m[1]);
}
📊 正则表达式速查表
| 语法 | 说明 | 示例 |
|---|---|---|
. |
任意字符 | /a.c/ 匹配 abc |
\d |
数字 | /\d+/ 匹配 123 |
\w |
单词字符 | /\w+/ 匹配 abc_123 |
\s |
空白字符 | /\s+/ 匹配空格 |
^ |
行首 | /^hello/ |
$ |
行尾 | /world$/ |
* |
0次或多次 | /a*/ |
+ |
1次或多次 | /a+/ |
? |
0次或1次 | /a?/ |
{n,m} |
n到m次 | /a{2,4}/ |
[] |
字符集 | /[abc]/ |
() |
分组 | /(ab)+/ |
| ` | ` | 或 |
(?=) |
先行断言 | /\d+(?=元)/ |
(?<=) |
后行断言 | /(?<=\$)\d+/ |
💡 总结
正则表达式核心要点:
- ✅ 掌握元字符 :
.\d\w\s等 - ✅ 理解量词 :
*+?{n,m}及贪婪/非贪婪 - ✅ 善用分组:捕获分组、非捕获分组、反向引用
- ✅ 活用断言:先行断言、后行断言
- ✅ 熟悉API:test、exec、match、replace
- ✅ 多练实战:验证、提取、替换、格式化
正则表达式是开发者必备技能,熟练掌握能大幅提升文本处理效率!
💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~