正则表达式中的选择、分组和引用是构建复杂模式匹配的三大核心功能。本文将深入解析这些概念及其在JavaScript中的具体应用。
选择使用管道符|表示"或"逻辑,匹配多个模式中的任意一个。
// 匹配 "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
分组使用圆括号()将部分模式组合在一起,主要用途有:
// 不加括号
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
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
使用(?:...)语法,分组但不捕获
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位
引用允许在同一个正则表达式中引用之前捕获的分组。
// 匹配成对的引号
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引入了命名捕获组,使正则表达式更易读。
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"
// 匹配连续重复的字符
const pattern = /(\w)\1+/g;
const text = "Look at the booook and the coooool code";
console.log(text.match(pattern)); // ["oo", "oooo", "oooo"]
// 匹配嵌套的括号(有限深度)
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
// 解析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>) 提高了代码可读性掌握这些概念后,你可以构建更复杂、更精确的正则表达式,有效处理文本匹配、验证和转换任务。在实际使用中,根据具体需求选择合适的功能,并注意性能优化。