JavaScript正则表达式:选择、分组与引用深度解析
正则表达式中的选择、分组和引用是构建复杂模式匹配的三大核心功能。本文将深入解析这些概念及其在JavaScript中的具体应用。
一、选择 (Alternation)
选择使用管道符|表示"或"逻辑,匹配多个模式中的任意一个。
基本用法
// 匹配 "cat" 或 "dog"
const pattern = /cat|dog/;
console.log(pattern.test("I have a cat")); // true
console.log(pattern.test("I have a dog")); // true
console.log(pattern.test("I have a bird")); // false
选择的范围
// 注意选择的范围
const pattern1 = /cat|dog house/; // 匹配 "cat" 或 "dog house"
const pattern2 = /(cat|dog) house/; // 匹配 "cat house" 或 "dog house"
console.log(pattern1.test("cat house")); // true (只匹配了"cat")
console.log(pattern2.test("cat house")); // true (匹配了整个"cat house")
优先级问题
// 选择操作符的优先级很低
const pattern = /^cat|dog$/;
// 相当于: (^cat) | (dog$)
// 匹配以"cat"开头的字符串 或 以"dog"结尾的字符串
console.log(pattern.test("cat")); // true
console.log(pattern.test("dog")); // true
console.log(pattern.test("catdog")); // true
console.log(pattern.test("dogcat")); // false
二、分组 (Grouping)
分组使用圆括号()将部分模式组合在一起,主要用途有:
1. 控制操作符作用范围
// 不加括号
const pattern1 = /ab+c/; // a后面跟着1个或多个b,然后是c
console.log(pattern1.test("abbbc")); // true
console.log(pattern1.test("abcabc")); // false
// 加括号
const pattern2 = /(ab)+c/; // 1个或多个"ab",然后是c
console.log(pattern2.test("abbbc")); // false
console.log(pattern2.test("abc")); // true
console.log(pattern2.test("ababc")); // true
2. 捕获分组 (Capturing Groups)
const pattern = /(\d{4})-(\d{2})-(\d{2})/;
const date = "2023-10-26";
const match = date.match(pattern);
console.log(match);
// [
// "2023-10-26",
// "2023", // 第一个捕获组
// "10", // 第二个捕获组
// "26" // 第三个捕获组
// ]
// 使用解构获取分组值
const [, year, month, day] = match;
console.log(year, month, day); // 2023 10 26
3. 非捕获分组 (Non-capturing Groups)
使用(?:...)语法,分组但不捕获
const pattern1 = /(\d{3})-(\d{4})/;
const pattern2 = /(?:\d{3})-(\d{4})/;
const phone = "123-4567";
const match1 = phone.match(pattern1);
console.log(match1); // ["123-4567", "123", "4567"]
const match2 = phone.match(pattern2);
console.log(match2); // ["123-4567", "4567"] - 只捕获了后4位
三、引用 (Backreferences)
引用允许在同一个正则表达式中引用之前捕获的分组。
基本引用语法
// 匹配成对的引号
const pattern = /(['"])(.*?)\1/;
console.log(pattern.test('"Hello"')); // true
console.log(pattern.test("'World'")); // true
console.log(pattern.test('"Oops\'')); // false (引号不匹配)
引用在替换中的应用
// 交换姓和名
const name = "Smith, John";
const swapped = name.replace(/(\w+),\s*(\w+)/, "$2 $1");
console.log(swapped); // "John Smith"
// 格式化日期
const date = "2023-10-26";
const formatted = date.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(formatted); // "10/26/2023"
嵌套分组的引用
// 匹配重复的单词
const pattern = /\b(\w+)\s+\1\b/;
console.log(pattern.test("hello hello")); // true
console.log(pattern.test("hello world")); // false
四、命名捕获分组和引用 (ES2018+)
ES2018引入了命名捕获组,使正则表达式更易读。
命名捕获组语法
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const date = "2023-10-26";
const match = date.match(pattern);
console.log(match.groups.year); // "2023"
console.log(match.groups.month); // "10"
console.log(match.groups.day); // "26"
命名引用
// 匹配成对引号(命名引用版本)
const pattern = /(?<quote>['"])(.*?)\k<quote>/;
console.log(pattern.test('"test"')); // true
console.log(pattern.test('"test\'')); // false
在替换中使用命名组
const date = "2023-10-26";
const formatted = date.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<month>/$<day>/$<year>"
);
console.log(formatted); // "10/26/2023"
五、高级应用示例
1. 匹配重复模式
// 匹配连续重复的字符
const pattern = /(\w)\1+/g;
const text = "Look at the booook and the coooool code";
console.log(text.match(pattern)); // ["oo", "oooo", "oooo"]
2. 平衡组(模拟)
// 匹配嵌套的括号(有限深度)
function matchParentheses(maxDepth) {
let pattern = '';
for (let i = 1; i <= maxDepth; i++) {
pattern += `(?:[^()]*\\([^()]*\\)[^()]*)${i === maxDepth ? '' : '|'}`;
}
return new RegExp(pattern);
}
const text = "a(b(c)d)e";
const pattern = matchParentheses(3);
console.log(pattern.test(text)); // true
3. 复杂数据提取
// 解析URL参数
function parseURLParams(url) {
const pattern = /(\w+)=([^&]*)/g;
const params = {};
let match;
while ((match = pattern.exec(url)) !== null) {
params[match[1]] = decodeURIComponent(match[2]);
}
return params;
}
const url = "page?name=John&age=30&city=New%20York";
console.log(parseURLParams(url));
// { name: "John", age: "30", city: "New York" }
六、性能注意事项
避免过度使用捕获组:如果不需要捕获内容,使用非捕获组
(?:...)
注意回溯问题:复杂的选择和分组可能导致性能问题
使用具体字符集:尽量使用
[abc]而不是
(a|b|c)
避免嵌套过多:深度嵌套的分组会影响性能
// 性能对比
const text = "a".repeat(1000);
// 较慢:使用选择
console.time("alternation");
/(a|b|c)+/.test(text);
console.timeEnd("alternation");
// 较快:使用字符集
console.time("character class");
/[abc]+/.test(text);
console.timeEnd("character class");
总结
JavaScript正则表达式中的选择、分组和引用提供了强大的模式匹配能力:
- 选择 (
|) 提供了逻辑"或"操作
- 分组 (
()) 用于组合模式和控制作用范围
- 引用 (
\1, \k<name>) 允许重用已匹配的内容
- 命名捕获组 (
?<name>) 提高了代码可读性
掌握这些概念后,你可以构建更复杂、更精确的正则表达式,有效处理文本匹配、验证和转换任务。在实际使用中,根据具体需求选择合适的功能,并注意性能优化。