深入理解JavaScript正则表达式
正则表达式(Regular Expression,常简称为regex或regexp)是处理字符串的强大工具,它提供了一种灵活的方式来搜索、匹配(以及在某些情况下替换)文本中的子字符串。在JavaScript中,正则表达式是通过RegExp
对象来表示的。本文将带你深入了解JavaScript中的正则表达式,包括其基础语法、常见模式、以及实际用例。
1. 正则表达式基础
1.1 创建正则表达式
在JavaScript中,你可以通过两种方式创建正则表达式:
- 字面量语法:使用斜杠(/)包围的模式,如
/ab+c/
。 - 构造函数:使用
new RegExp('ab+c')
。
1.1.1 字面量语法
字面量语法是创建正则表达式最直接的方式。你只需要将模式放在斜杠(/)之间即可。例如,如果你想匹配字符串中所有出现的“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”。
1.1.2 构造函数
另一种创建正则表达式的方式是使用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/; // 直接使用字面量语法,通常更可取
在实际开发中,如果正则表达式的模式是固定的,推荐使用字面量语法,因为它的语法更简洁,性能也更好。如果模式需要根据运行时的情况动态生成,那么构造函数是一个更好的选择。
1.2 匹配模式
1.2.1 全局模式(g)
全局模式意味着正则表达式会搜索整个字符串以找到所有匹配项,而不仅仅是找到第一个匹配项就停止。如果没有使用全局模式,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”。
1.2.2 不区分大小写模式(i)
不区分大小写模式使正则表达式在匹配时忽略字符的大小写。这意味着它会将大写和小写字符视为相同。
代码示例:
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”。
1.2.3 多行模式(m)
多行模式改变了^
和$
的行为。在默认的单行模式下,^
匹配字符串的开始,$
匹配字符串的结束。但在多行模式下,^
和$
也会匹配任何行的开始和结束。
代码示例:
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”,不区分大小写。
1.3 特殊字符和字符类
1.3.1 特殊字符和字符类
- \d:匹配任何数字字符,等同于
[0-9]
。 - \w:匹配任何字母、数字或下划线字符,等同于
[A-Za-z0-9_]
。 - .:匹配除换行符(
\n
、\r
)之外的任何字符。 - *:匹配前面的子表达式零次或多次。
- +:匹配前面的子表达式一次或多次。
- ?:匹配前面的子表达式零次或一次。
- {n}:n 是一个非负整数,匹配前面的子表达式恰好 n 次。
- {n,}:n 是一个非负整数,匹配前面的子表达式至少 n 次。
- {n,m}:m 和 n 均为非负整数,且 n <= m,匹配前面的子表达式至少 n 次且最多 m 次。
代码示例
// \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” |
这个图表总结了特殊字符和字符类的用法,以及它们如何与数量词结合使用来定义更具体的匹配模式。在实际应用中,这些特殊字符和字符类可以单独使用,也可以组合使用,以满足特定的匹配需求。