正则表达式(Regular Expression,常简称为regex或regexp)是处理字符串的强大工具,它提供了一种灵活的方式来搜索、匹配(以及在某些情况下替换)文本中的子字符串。在JavaScript中,正则表达式是通过RegExp
对象来表示的。本文将带你深入了解JavaScript中的正则表达式,包括其基础语法、常见模式、以及实际用例。
在JavaScript中,你可以通过两种方式创建正则表达式:
/ab+c/
。new RegExp('ab+c')
。字面量语法是创建正则表达式最直接的方式。你只需要将模式放在斜杠(/)之间即可。例如,如果你想匹配字符串中所有出现的“ab+c”模式(即一个“a”后面跟着一个或多个“b”,再后面是一个“c”),你可以这样写:
const regex = /ab+c/;
这个正则表达式可以用来测试字符串中是否存在符合该模式的子串:
const str = 'abc abbc abbbbbc';
const result = str.match(regex);
console.log(result); // 输出: [ 'abc', index: 0, input: 'abc abbc abbbbbc', groups: undefined ]
注意,在这个例子中,match
方法返回了一个数组,其中包含了匹配到的所有结果。由于我们使用了贪婪量词(+
),它会尽可能多地匹配“b”。然而,正则表达式是按照左到右的顺序进行匹配的,所以第一个匹配到的是“abc”。
另一种创建正则表达式的方式是使用RegExp
构造函数。这种方式在模式需要动态生成时特别有用。构造函数接受两个参数:模式字符串和可选的标志字符串。例如:
const pattern = 'ab+c';
const flags = 'i'; // 不区分大小写
const regex = new RegExp(pattern, flags);
这个正则表达式与之前的字面量语法创建的等价,但是它不区分大小写:
const str = 'aBc AbBc aBBBBc';
const result = str.match(regex);
console.log(result); // 输出: [ 'aBc', index: 0, input: 'aBc AbBc aBBBBc', groups: undefined ]
在这个例子中,由于我们设置了“i”标志,所以正则表达式在匹配时忽略了大小写。因此,它能够匹配到“aBc”。
请注意,当使用构造函数创建正则表达式时,由于字符串中的反斜杠(\)是转义字符,你可能需要对它进行双重转义。例如,如果你想在构造函数中表示\d
(匹配任何数字),你需要这样写:
const regex = new RegExp('\\d');
或者,你可以使用原始字符串字面量(在ES6及更高版本中可用),这样就不需要对反斜杠进行转义:
const regex = new RegExp(`\\d`); // 实际上不需要双重转义,但这里为了演示
// 或者更简洁地
const regex = /\d/; // 直接使用字面量语法,通常更可取
在实际开发中,如果正则表达式的模式是固定的,推荐使用字面量语法,因为它的语法更简洁,性能也更好。如果模式需要根据运行时的情况动态生成,那么构造函数是一个更好的选择。
全局模式意味着正则表达式会搜索整个字符串以找到所有匹配项,而不仅仅是找到第一个匹配项就停止。如果没有使用全局模式,match
方法在找到第一个匹配项后就会返回,不会再继续搜索字符串的剩余部分。
代码示例:
const regexNonGlobal = /foo/; // 非全局模式
const regexGlobal = /foo/g; // 全局模式
const str = 'foo and foo';
const matchesNonGlobal = str.match(regexNonGlobal);
const matchesGlobal = str.match(regexGlobal);
console.log(matchesNonGlobal); // 输出: [ 'foo', index: 0, input: 'foo and foo', groups: undefined ]
console.log(matchesGlobal); // 输出: [ 'foo', 'foo' ]
在这个例子中,非全局模式的正则表达式只匹配到了第一个“foo”,而全局模式的正则表达式匹配到了所有出现的“foo”。
不区分大小写模式使正则表达式在匹配时忽略字符的大小写。这意味着它会将大写和小写字符视为相同。
代码示例:
const regexCaseSensitive = /FOO/; // 区分大小写
const regexCaseInsensitive = /FOO/i; // 不区分大小写
const str = 'FOO and foo';
const matchesCaseSensitive = str.match(regexCaseSensitive);
const matchesCaseInsensitive = str.match(regexCaseInsensitive);
console.log(matchesCaseSensitive); // 输出: [ 'FOO', index: 0, input: 'FOO and foo', groups: undefined ]
console.log(matchesCaseInsensitive); // 输出: [ 'FOO', 'foo' ]
在这个例子中,区分大小写的正则表达式只匹配到了大写的“FOO”,而不区分大小写的正则表达式匹配到了所有出现的“FOO”和“foo”。
多行模式改变了^
和$
的行为。在默认的单行模式下,^
匹配字符串的开始,$
匹配字符串的结束。但在多行模式下,^
和$
也会匹配任何行的开始和结束。
代码示例:
const regexSingleLine = /^foo/; // 单行模式
const regexMultiLine = /^foo/m; // 多行模式
const str = 'foo\nfoo';
const matchesSingleLine = str.match(regexSingleLine);
const matchesMultiLine = str.match(regexMultiLine);
console.log(matchesSingleLine); // 输出: [ 'foo', index: 0, input: 'foo\nfoo', groups: undefined ]
console.log(matchesMultiLine); // 在某些环境中可能不会按预期工作,因为match()方法不完全支持多行模式
// 使用exec()方法在多行字符串中查找所有匹配项
let regexMultiLineExec = /^foo/m;
let result;
while ((result = regexMultiLineExec.exec(str)) !== null) {
console.log(result[0]); // 输出每行匹配的'foo'
}
// 输出:
// foo
// foo
注意:在使用match
方法时,多行模式可能不会按预期工作,因为match
方法返回的是整个字符串中的匹配项,而不是每行一个。为了在多行字符串中查找每行的匹配项,通常使用exec
方法在循环中多次调用。
模式 | 描述 | 示例 |
---|---|---|
全局(g) | 搜索整个字符串以找到所有匹配项 | /foo/g |
不区分大小写(i) | 在匹配时忽略大小写 | /FOO/i |
多行(m) | 使^ 和$ 匹配任何行的开始和结束 | /^foo/m (使用exec 方法循环查找) |
在实际应用中,这些模式可以单独使用,也可以组合使用,以满足特定的匹配需求。例如,/foo/gi
会匹配字符串中所有出现的“foo”,不区分大小写。
[0-9]
。[A-Za-z0-9_]
。\n
、\r
)之外的任何字符。// \d 示例:匹配数字
const regexDigits = /\d+/;
const strDigits = '123abc456';
const matchDigits = strDigits.match(regexDigits);
console.log(matchDigits[0]); // 输出: "123" (只会匹配到第一个连续的数字序列)
// \w 示例:匹配字母、数字或下划线
const regexWordChars = /\w+/;
const strWordChars = 'hello_world123';
const matchWordChars = strWordChars.match(regexWordChars);
console.log(matchWordChars[0]); // 输出: "hello_world123"
// . 示例:匹配任意字符(换行符除外)
const regexAnyChar = /a.b/;
const strAnyChar = 'axb\nayb';
const matchAnyChar = strAnyChar.match(regexAnyChar);
console.log(matchAnyChar[0]); // 输出: "axb" (不会匹配换行后的"ayb")
// * 示例:匹配前面的字符零次或多次
const regexStar = /ab*c/;
const strStar = 'acabcabbc';
const matchStar = strStar.match(regexStar);
console.log(matchStar[0]); // 输出: "abbc" (匹配尽可能多的"b")
// + 示例:匹配前面的字符一次或多次
const regexPlus = /ab+c/;
const strPlus = 'acabcabbc';
const matchPlus = strPlus.match(regexPlus);
console.log(matchPlus[0]); // 输出: "abbc" (至少匹配一次"b")
// ? 示例:匹配前面的字符零次或一次
const regexQuestion = /ab?c/;
const strQuestion = 'acbabc';
const matchQuestion = strQuestion.match(regexQuestion);
console.log(matchQuestion[0]); // 输出: "ac" (可以没有"b")
// {n} 示例:匹配前面的字符恰好 n 次
const regexExact = /ab{2}c/;
const strExact = 'abbcabcab';
const matchExact = strExact.match(regexExact);
console.log(matchExact[0]); // 输出: "abbc" (恰好匹配两次"b")
// {n,} 示例:匹配前面的字符至少 n 次
const regexAtLeast = /ab{2,}c/;
const strAtLeast = 'abbcabbbbc';
const matchAtLeast = strAtLeast.match(regexAtLeast);
console.log(matchAtLeast[0]); // 输出: "abbbbc" (至少匹配两次"b",尽可能多地匹配)
// {n,m} 示例:匹配前面的字符至少 n 次,但不超过 m 次
const regexRange = /ab{2,4}c/;
const strRange = 'abbcabbbbcabbbbbc';
const matchRange = strRange.match(regexRange);
console.log(matchRange[0]); // 输出: "abbbbc" (匹配两到四次"b",尽可能多地匹配)
特殊字符/字符类 | 描述 | 示例 |
---|---|---|
\d | 匹配任何数字字符 | \d 匹配 “1”, “2”, “3” 等 |
\w | 匹配任何字母、数字或下划线字符 | \w 匹配 “a”, “B”, “5”, “_” 等 |
. | 匹配除换行符之外的任何字符 | . 匹配 “a”, “b”, “c” 但不匹配 “\n” |
* | 匹配前面的子表达式零次或多次 | ab*c 匹配 “ac”, “abc”, “abbc” 等 |
+ | 匹配前面的子表达式一次或多次 | ab+c 匹配 “abc”, “abbc” 但不匹配 “ac” |
? | 匹配前面的子表达式零次或一次 | ab?c 匹配 “ac”, “abc” 但不匹配 “abbc” |
{n} | 匹配前面的子表达式恰好 n 次 | ab{2}c 匹配 “abbc” 但不匹配 “abc” 或 “abbbc” |
{n,} | 匹配前面的子表达式至少 n 次 | ab{2,}c 匹配 “abbc”, “abbbbc” 等,但不匹配 “abc” |
{n,m} | 匹配前面的子表达式至少 n 次且最多 m 次 | ab{2,4}c 匹配 “abbc”, “abbbbc” 但不匹配 “abc” 或 “abbbbbbc” |
这个图表总结了特殊字符和字符类的用法,以及它们如何与数量词结合使用来定义更具体的匹配模式。在实际应用中,这些特殊字符和字符类可以单独使用,也可以组合使用,以满足特定的匹配需求。