本文将系统讲解正则表达式的核心语法和实战技巧,通过大量实例帮助你掌握这一强大的文本处理工具,提升开发效率。
📋 目录
一、正则表达式基础
1.1 创建正则表达式
javascript
// 字面量方式(推荐)
const regex1 = /pattern/flags;
// 构造函数方式(动态创建)
const regex2 = new RegExp('pattern', 'flags');
// 示例
const emailRegex = /^\S+@\S+\.\S+$/;
const dynamicRegex = new RegExp(`^${userInput}$`, 'i');
1.2 常用标志(Flags)
| 标志 | 说明 | 示例 |
|---|---|---|
| g | 全局匹配 | /a/g 匹配所有a |
| i | 忽略大小写 | /a/i 匹配a和A |
| m | 多行模式 | ^$匹配每行开头结尾 |
| s | dotAll模式 | .匹配包括换行符 |
| u | Unicode模式 | 正确处理Unicode |
| y | 粘性匹配 | 从lastIndex开始匹配 |
javascript
// 标志组合使用
const regex = /hello/gi; // 全局、忽略大小写
'Hello hello HELLO'.match(/hello/gi);
// ['Hello', 'hello', 'HELLO']
1.3 基本匹配
javascript
// 精确匹配
/hello/.test('hello world'); // true
// 字符类
/[abc]/.test('apple'); // true,匹配a、b或c中任一个
/[a-z]/.test('hello'); // true,匹配小写字母
/[A-Z]/.test('Hello'); // true,匹配大写字母
/[0-9]/.test('123'); // true,匹配数字
/[a-zA-Z0-9]/.test('a1'); // true,匹配字母数字
// 否定字符类
/[^abc]/.test('def'); // true,匹配非a、b、c的字符
/[^0-9]/.test('abc'); // true,匹配非数字
二、元字符与量词
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('hello world'); // true
2.2 量词
javascript
// 基本量词
* // 0次或多次
+ // 1次或多次
? // 0次或1次
{n} // 恰好n次
{n,} // 至少n次
{n,m} // n到m次
// 示例
/a*/.test(''); // true,0次
/a+/.test('aaa'); // true,1次或多次
/a?/.test('b'); // true,0次或1次
/a{3}/.test('aaa'); // true,恰好3次
/a{2,4}/.test('aaa'); // true,2到4次
2.3 贪婪与非贪婪
javascript
// 贪婪模式(默认)- 尽可能多匹配
const greedy = /<.+>/;
'<div>content</div>'.match(greedy);
// ['<div>content</div>']
// 非贪婪模式 - 尽可能少匹配
const lazy = /<.+?>/;
'<div>content</div>'.match(lazy);
// ['<div>']
// 非贪婪量词
*? // 0次或多次(非贪婪)
+? // 1次或多次(非贪婪)
?? // 0次或1次(非贪婪)
{n,m}? // n到m次(非贪婪)
2.4 边界匹配
javascript
^ // 字符串开头(多行模式下匹配行开头)
$ // 字符串结尾(多行模式下匹配行结尾)
\b // 单词边界
\B // 非单词边界
// 示例
/^hello/.test('hello world'); // true
/world$/.test('hello world'); // true
/\bword\b/.test('a word here'); // true
/\Bword/.test('password'); // true
三、分组与引用
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' (第一个分组)
// match[2] = '01' (第二个分组)
// match[3] = '15' (第三个分组)
// 命名分组
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?|ftp):\/\/(\S+)/;
const match = 'https://example.com'.match(regex);
// match[1] = 'example.com' (只捕获域名)
// 对比捕获分组
const captureRegex = /(https?|ftp):\/\/(\S+)/;
const captureMatch = 'https://example.com'.match(captureRegex);
// captureMatch[1] = 'https'
// captureMatch[2] = 'example.com'
3.3 反向引用
javascript
// 引用前面的捕获分组
const regex = /(['"]).*?\1/; // \1 引用第一个分组
regex.test('"hello"'); // true
regex.test("'hello'"); // true
regex.test('"hello\''); // false,引号不匹配
// 命名反向引用
const namedRegex = /(?<quote>['"]).*?\k<quote>/;
namedRegex.test('"hello"'); // true
// 替换中使用引用
'hello world'.replace(/(\w+) (\w+)/, '$2 $1');
// 'world hello'
// 命名引用替换
'2024-01-15'.replace(
/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
'$<d>/$<m>/$<y>'
);
// '15/01/2024'
3.4 选择分支
javascript
// 或运算 |
const regex = /cat|dog|bird/;
regex.test('I have a cat'); // true
regex.test('I have a dog'); // true
// 分组中的选择
const urlRegex = /https?:\/\/(www\.)?example\.(com|org|net)/;
urlRegex.test('https://example.com'); // true
urlRegex.test('https://www.example.org'); // true
四、断言与边界
4.1 先行断言
javascript
// 正向先行断言 (?=...)
// 匹配后面是...的位置
const regex = /\d+(?=元)/;
'100元'.match(regex); // ['100']
'100$'.match(regex); // null
// 负向先行断言 (?!...)
// 匹配后面不是...的位置
const regex2 = /\d+(?!元)/;
'100$'.match(regex2); // ['100']
'100元'.match(regex2); // ['10'] (匹配到10,因为0后面是元)
4.2 后行断言
javascript
// 正向后行断言 (?<=...)
// 匹配前面是...的位置
const regex = /(?<=\$)\d+/;
'$100'.match(regex); // ['100']
'€100'.match(regex); // null
// 负向后行断言 (?<!...)
// 匹配前面不是...的位置
const regex2 = /(?<!\$)\d+/;
'€100'.match(regex2); // ['100']
'$100'.match(regex2); // ['00']
4.3 断言实战
javascript
// 密码强度验证:至少包含大写、小写、数字
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
strongPassword.test('Abc12345'); // true
strongPassword.test('abc12345'); // false,缺少大写
// 千分位格式化
const addCommas = (num) => {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
addCommas(1234567); // '1,234,567'
// 提取不在引号内的内容
const regex = /\w+(?=(?:[^"]*"[^"]*")*[^"]*$)/g;
五、JavaScript正则API
5.1 RegExp方法
javascript
// test() - 测试是否匹配
/\d+/.test('abc123'); // true
// exec() - 执行匹配,返回详细信息
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const result = regex.exec('2024-01-15');
// result[0] = '2024-01-15'
// result[1] = '2024'
// result.index = 0
// result.input = '2024-01-15'
// 全局匹配迭代
const globalRegex = /\d+/g;
let match;
while ((match = globalRegex.exec('a1b2c3')) !== null) {
console.log(match[0], match.index);
}
// '1' 1
// '2' 3
// '3' 5
5.2 String方法
javascript
// match() - 匹配
'hello world'.match(/\w+/); // ['hello']
'hello world'.match(/\w+/g); // ['hello', 'world']
// matchAll() - 返回迭代器(ES2020)
const matches = 'a1b2c3'.matchAll(/(\w)(\d)/g);
for (const match of matches) {
console.log(match[0], match[1], match[2]);
}
// 'a1' 'a' '1'
// 'b2' 'b' '2'
// 'c3' 'c' '3'
// search() - 返回首次匹配位置
'hello world'.search(/world/); // 6
'hello world'.search(/xyz/); // -1
// replace() - 替换
'hello world'.replace(/world/, 'regex'); // 'hello regex'
'aaa'.replace(/a/g, 'b'); // 'bbb'
// 替换函数
'hello world'.replace(/\w+/g, (match) => {
return match.toUpperCase();
});
// 'HELLO WORLD'
// 替换函数参数
'2024-01-15'.replace(
/(\d{4})-(\d{2})-(\d{2})/,
(match, year, month, day) => {
return `${day}/${month}/${year}`;
}
);
// '15/01/2024'
// replaceAll() - 替换所有(ES2021)
'aaa'.replaceAll('a', 'b'); // 'bbb'
// split() - 分割
'a,b;c|d'.split(/[,;|]/); // ['a', 'b', 'c', 'd']
'a1b2c3'.split(/\d/); // ['a', 'b', 'c', '']
5.3 常用技巧
javascript
// 转义特殊字符
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 动态创建正则
function createSearchRegex(keyword) {
const escaped = escapeRegExp(keyword);
return new RegExp(escaped, 'gi');
}
// 高亮搜索关键词
function highlightKeyword(text, keyword) {
const regex = createSearchRegex(keyword);
return text.replace(regex, '<mark>$&</mark>');
}
highlightKeyword('Hello World', 'world');
// 'Hello <mark>World</mark>'
六、实战案例大全
6.1 表单验证
javascript
// 手机号(中国大陆)
const phoneRegex = /^1[3-9]\d{9}$/;
// 邮箱
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// 身份证号
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]+$/;
// 用户名(字母开头,允许字母数字下划线,4-16位)
const usernameRegex = /^[a-zA-Z][a-zA-Z0-9_]{3,15}$/;
// 银行卡号(16-19位数字)
const bankCardRegex = /^\d{16,19}$/;
// 邮政编码
const postalCodeRegex = /^[1-9]\d{5}$/;
6.2 数据提取
javascript
// 提取URL中的参数
function getUrlParams(url) {
const params = {};
const regex = /[?&]([^=&#]+)=([^&#]*)/g;
let match;
while ((match = regex.exec(url)) !== null) {
params[match[1]] = decodeURIComponent(match[2]);
}
return params;
}
getUrlParams('https://example.com?name=john&age=25');
// { name: 'john', age: '25' }
// 提取HTML标签内容
function extractTagContent(html, tagName) {
const regex = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'gi');
const matches = [];
let match;
while ((match = regex.exec(html)) !== null) {
matches.push(match[1].trim());
}
return matches;
}
extractTagContent('<p>Hello</p><p>World</p>', 'p');
// ['Hello', 'World']
// 提取所有链接
function extractLinks(html) {
const regex = /href=["']([^"']+)["']/gi;
const links = [];
let match;
while ((match = regex.exec(html)) !== null) {
links.push(match[1]);
}
return links;
}
// 提取日期
function extractDates(text) {
const regex = /\d{4}[-/年]\d{1,2}[-/月]\d{1,2}日?/g;
return text.match(regex) || [];
}
extractDates('会议时间:2024-01-15 和 2024/02/20');
// ['2024-01-15', '2024/02/20']
6.3 文本处理
javascript
// 去除HTML标签
function stripHtml(html) {
return html.replace(/<[^>]*>/g, '');
}
stripHtml('<p>Hello <b>World</b></p>');
// 'Hello World'
// 驼峰转下划线
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, letter => letter.toUpperCase());
}
capitalize('hello world'); // 'Hello World'
// 压缩空白字符
function compressWhitespace(str) {
return str.replace(/\s+/g, ' ').trim();
}
compressWhitespace(' hello world '); // 'hello world'
// 格式化数字(千分位)
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
formatNumber(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('example@gmail.com'); // 'ex***@gmail.com'
function maskIdCard(idCard) {
return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
}
maskIdCard('110101199001011234'); // '110101********1234'
6.4 代码处理
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*)?\(|(\w+)\s*:\s*(?:async\s*)?\(/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+as\s+\w+)\s+from\s+['"]([^'"]+)['"]/g;
const imports = [];
let match;
while ((match = regex.exec(code)) !== null) {
imports.push(match[1]);
}
return imports;
}
6.5 日志分析
javascript
// 解析Nginx日志
function parseNginxLog(line) {
const regex = /^(\S+) - - \[([^\]]+)\] "(\w+) ([^"]+)" (\d+) (\d+)/;
const match = line.match(regex);
if (match) {
return {
ip: match[1],
time: match[2],
method: match[3],
url: match[4],
status: parseInt(match[5]),
size: parseInt(match[6])
};
}
return null;
}
// 提取错误日志
function extractErrors(logs) {
const regex = /\[ERROR\]\s*(.+)/gi;
return logs.match(regex) || [];
}
// 统计IP访问次数
function countIpVisits(logs) {
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}/gm;
const ips = logs.match(ipRegex) || [];
return ips.reduce((acc, ip) => {
acc[ip] = (acc[ip] || 0) + 1;
return acc;
}, {});
}
📊 正则表达式速查表
| 语法 | 说明 | 示例 |
|---|---|---|
. |
任意字符 | 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] |
字符类 | [aeiou] |
[^abc] |
否定字符类 | [^0-9] |
(...) |
捕获分组 | (\d+) |
(?:...) |
非捕获分组 | (?:\d+) |
(?=...) |
正向先行断言 | \d+(?=元) |
(?!...) |
负向先行断言 | \d+(?!元) |
| ` | ` | 或 |
💡 总结
正则表达式核心要点:
- ✅ 掌握基础语法:字符类、量词、边界
- ✅ 理解分组引用:捕获分组、反向引用
- ✅ 灵活使用断言:先行断言、后行断言
- ✅ 熟悉JS API:test、exec、match、replace
- ✅ 贪婪与非贪婪:理解匹配模式
- ✅ 实战积累:表单验证、数据提取、文本处理
正则表达式是开发者必备技能,熟练掌握能大幅提升开发效率!
💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~