正则表达式
JavaScript正则表达式语法、常用模式、RegExp对象、字符串方法与实战技巧详解。
1. 正则表达式基础
1.1 创建正则表达式
// 方式1:字面量(推荐)
const pattern1 = /hello/i;
// 方式2:构造函数(动态生成时使用)
const keyword = 'hello';
const pattern2 = new RegExp(keyword, 'i');
// 修饰符
// g: 全局匹配(找到所有匹配)
// i: 忽略大小写
// m: 多行模式(^和$匹配行首行尾)
// s: dotAll模式(.匹配换行符)
// u: Unicode模式
// y: 粘连模式(从lastIndex位置开始匹配)
1.2 基本语法
// 字符类
/[abc]/ // 匹配a、b或c中任意一个
/[^abc]/ // 匹配除了a、b、c之外的字符
/[a-z]/ // 匹配小写字母
/[A-Z]/ // 匹配大写字母
/[0-9]/ // 匹配数字
/[a-zA-Z0-9]/ // 匹配字母和数字
// 预定义字符类
/./ // 匹配任意字符(除换行符,除非s修饰符)
/\d/ // 匹配数字 [0-9]
/\D/ // 匹配非数字 [^0-9]
/\w/ // 匹配单词字符 [a-zA-Z0-9_]
/\W/ // 匹配非单词字符
/\s/ // 匹配空白字符(空格、制表符、换行等)
/\S/ // 匹配非空白字符
// 量词
/x*/ // 匹配0次或多次
/x+/ // 匹配1次或多次
/x?/ // 匹配0次或1次
/x{n}/ // 匹配恰好n次
/x{n,}/ // 匹配至少n次
/x{n,m}/ // 匹配n到m次
// 贪婪 vs 非贪婪
/x*/ // 贪婪:尽可能多匹配
/x*?/ // 非贪婪:尽可能少匹配
// 边界
/^hello/ // 匹配以hello开头
/world$/ // 匹配以world结尾
/\bword\b/ // 匹配完整单词word
// 分组与引用
/(ab)+/ // 匹配ab一次或多次
/(a)(b)\1\2/ // \1引用第一个分组a,\2引用第二个分组b
/(?:ab)+/ // 非捕获分组,不记录匹配内容
// 或操作
/cat|dog/ // 匹配cat或dog
// 前瞻后顾
/x(?=y)/ // 正向前瞻:x后面紧跟y
/x(?!y)/ // 负向前瞻:x后面不跟y
/(?<=y)x/ // 正向后顾:x前面是y(ES2018)
/(?<!y)x/ // 负向后顾:x前面不是y(ES2018)
2. RegExp 对象方法
2.1 test 与 exec
// test: 检测是否匹配,返回布尔值
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailPattern.test('user@example.com')); // true
console.log(emailPattern.test('invalid-email')); // false
// exec: 返回匹配结果数组,包含分组信息
const datePattern = /(\d{4})-(\d{2})-(\d{2})/;
const result = datePattern.exec('Today is 2026-06-13');
console.log(result);
// ["2026-06-13", "2026", "06", "13"]
// result[0] = 完整匹配
// result[1] = 第一个分组
// result[2] = 第二个分组
// result[3] = 第三个分组
// result.index = 匹配开始位置
// result.input = 原始字符串
// 全局匹配时使用循环
const globalPattern = /\d+/g;
const text = 'a1b22c333';
let match;
while ((match = globalPattern.exec(text)) !== null) {
console.log(`Found ${match[0]} at index ${match.index}`);
// Found 1 at index 1
// Found 22 at index 3
// Found 333 at index 6
}
2.2 lastIndex 与粘连模式
// g修饰符的lastIndex
const pattern = /ab/g;
const str = 'ababab';
console.log(pattern.lastIndex); // 0
console.log(pattern.test(str)); // true, lastIndex变为2
console.log(pattern.test(str)); // true, lastIndex变为4
console.log(pattern.test(str)); // true, lastIndex变为6
console.log(pattern.test(str)); // false, lastIndex重置为0
// y修饰符(粘连模式):只从lastIndex位置匹配
const sticky = /ab/y;
sticky.lastIndex = 2;
console.log(sticky.test('abab')); // true,从位置2开始匹配"ab"
sticky.lastIndex = 1;
console.log(sticky.test('abab')); // false,位置1不是"ab"
3. 字符串方法
3.1 常用字符串正则方法
const text = 'Hello World, hello JavaScript';
// match: 返回匹配结果
console.log(text.match(/hello/i));
// ["Hello", index: 0, input: "Hello World, hello JavaScript", groups: undefined]
console.log(text.match(/hello/gi));
// ["Hello", "hello"]
console.log(text.match(/xyz/));
// null
// matchAll: 返回所有匹配的迭代器(ES2020)
const matches = [...text.matchAll(/he(l)(l)o/gi)];
for (const match of matches) {
console.log(`Match: ${match[0]}, Groups: ${match[1]},${match[2]}`);
}
// search: 返回第一个匹配的索引
console.log(text.search(/World/)); // 6
console.log(text.search(/xyz/)); // -1
// replace: 替换匹配内容
console.log(text.replace(/hello/i, 'Hi'));
// "Hi World, hello JavaScript"
console.log(text.replace(/hello/gi, 'Hi'));
// "Hi World, Hi JavaScript"
// 使用回调函数进行替换
const result = text.replace(/hello/gi, (match, offset, string) => {
return match.toUpperCase();
});
console.log(result); // "HELLO World, HELLO JavaScript"
// 使用分组引用
const dateStr = '2026-06-13';
const formatted = dateStr.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1');
console.log(formatted); // "13/06/2026"
// split: 使用正则分割字符串
const csv = 'one,two;three|four';
const parts = csv.split(/[,;|]/);
console.log(parts); // ["one", "two", "three", "four"]
4. 实用正则模式
4.1 表单验证
// 邮箱验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// 手机号验证(中国大陆)
const phoneRegex = /^1[3-9]\d{9}$/;
// 身份证号(18位)
const idCardRegex = /^\d{17}[\dXx]$/;
// 密码强度(至少8位,包含大小写字母和数字)
const strongPasswordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
// URL验证
const urlRegex =
/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
// IPv4地址
const ipv4Regex = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
// 中文字符
const chineseRegex = /^[\u4e00-\u9fa5]+$/;
// 验证函数
function validate(value, pattern, message) {
if (!pattern.test(value)) {
return { valid: false, message };
}
return { valid: true };
}
console.log(validate('user@example.com', emailRegex, '邮箱格式不正确'));
console.log(validate('13800138000', phoneRegex, '手机号格式不正确'));
4.2 文本处理
// 提取所有URL
const text = 'Visit https://example.com and http://test.org for more info.';
const urls = text.match(/https?:\/\/[^\s]+/g);
console.log(urls); // ["https://example.com", "http://test.org"]
// 提取HTML标签内容
const html = '<div class="main"><h1>Title</h1><p>Content</p></div>';
const tags = [...html.matchAll(/<(\w+)[^>]*>(.*?)<\/\1>/gs)];
for (const tag of tags) {
console.log(`Tag: ${tag[1]}, Content: ${tag[2]}`);
}
// 去除HTML标签
const cleanText = html.replace(/<[^>]+>/g, '');
console.log(cleanText); // "TitleContent"
// 千分位格式化
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
console.log(formatNumber(1234567890)); // "1,234,567,890"
// 驼峰转下划线
function camelToSnake(str) {
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
console.log(camelToSnake('myVariableName')); // "my_variable_name"
// 下划线转驼峰
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
console.log(snakeToCamel('my_variable_name')); // "myVariableName"
// 模板引擎简单实现
function render(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match;
});
}
const tpl = 'Hello, {{name}}! You are {{age}} years old.';
console.log(render(tpl, { name: 'Alice', age: 25 }));
// "Hello, Alice! You are 25 years old."
4.3 命名捕获组(ES2018)
// 使用命名捕获组
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = datePattern.exec('2026-06-13');
console.log(result.groups.year); // "2026"
console.log(result.groups.month); // "06"
console.log(result.groups.day); // "13"
// 在replace中使用命名捕获组
const formatted = '2026-06-13'.replace(datePattern, '$<day>/$<month>/$<year>');
console.log(formatted); // "13/06/2026"
5. 性能优化
5.1 预编译正则
// 避免在循环中重复创建正则
function badPractice(items) {
return items.filter((item) => /^[a-z]+$/.test(item)); // 每次都创建新正则
}
function goodPractice(items) {
const pattern = /^[a-z]+$/; // 预编译
return items.filter((item) => pattern.test(item));
}
5.2 避免灾难性回溯
// 危险:嵌套量词可能导致指数级回溯
const dangerous = /(a+)+b/;
// 安全:使用占有量词或原子组(部分引擎支持)
// 或重构正则避免嵌套量词
const safe = /a+b/;
6. 常见问题与解决方案
6.1 正则中的特殊字符转义
// 转义正则特殊字符
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const userInput = 'price: $50';
const escaped = escapeRegExp(userInput);
const regex = new RegExp(escaped, 'g');
console.log(regex.test('The price: $50 is correct')); // true
6.2 Unicode 匹配
// u修饰符处理Unicode
const emoji = '';
console.log(/^.$/.test(emoji)); // false(emoji是2个UTF-16码元)
console.log(/^.$/u.test(emoji)); // true(u修饰符正确处理)
// Unicode属性转义(ES2018)
const greek = /\p{Script=Greek}/u;
console.log(greek.test('α')); // true
const emojiPattern = /\p{Emoji}/u;
console.log(emojiPattern.test('')); // true
7. 总结与最佳实践
7.1 核心要点
- 字面量 vs 构造函数:静态正则用字面量,动态正则用构造函数
- 贪婪 vs 非贪婪:默认贪婪,加
?变非贪婪 - 分组:捕获分组用
(),非捕获用(?:) - 修饰符:
g全局、i忽略大小写、m多行、sdotAll
7.2 最佳实践
- 预编译正则:避免在循环中重复创建
- 使用命名捕获组:提高可读性
- 转义用户输入:防止注入
- 避免过度复杂的正则:难以维护,考虑分步处理
- 使用在线工具测试:如 regex101.com、regexr.com
- 添加注释:复杂正则使用
x修饰符或拆分注释