JS 正则表达式全攻略:前端开发必备
正则表达式(Regular Expression,简称 regex 或 regexp)是前端开发中不可或缺的强大工具,用于模式匹配和文本处理。无论是验证用户输入、从字符串中提取数据,还是进行高级的文本操作,掌握正则表达式都能显著提高开发效率和代码质量。
1. 什么是正则表达式?
正则表达式是一种定义搜索模式的字符序列,它描述了字符串中字符组合的模式。在 JavaScript 中,正则表达式是 RegExp 类的实例对象,它结合了普通字符(字面字符)和特殊字符(元字符)来构成匹配规则。
2. 创建正则表达式
在 JavaScript 中,创建正则表达式主要有两种方式:
2.1 字面量方式 (Literal Notation)
当正则表达式保持不变时,使用字面量方式性能更优,也更简洁。
javascript
const re = /pattern/flags;
// 示例:匹配任意一个或多个非空白字符,后跟 "@" 符号,再后跟任意一个或多个非空白字符,
// 再后跟一个点号,最后再后跟任意一个或多个非空白字符。
const emailRegex = /\S+@\S+\.\S+/;
2.2 RegExp 构造函数 (Constructor)
当正则表达式模式会动态改变,或者模式字符串来自用户输入等外部来源时,应使用构造函数。需要注意的是,在构造函数中,反斜杠 \ 等特殊字符需要进行双重转义(\\)。
javascript
const re = new RegExp("pattern", "flags");
// 示例:动态构建一个匹配 "user" 后跟一个或多个数字的正则表达式
const userId = "user";
const dynamicPattern = userId + "\\d+"; // 反斜杠需要转义
const userRegex = new RegExp(dynamicPattern, "g");
3. 正则表达式的基本组成
正则表达式由普通字符和特殊字符(元字符)组成,并可通过修饰符调整匹配行为。
3.1 普通字符
普通字符会直接匹配文本中的对应字符。例如,/abc/ 会精确匹配字符串中的 “abc”。
3.2 特殊字符 (元字符)
元字符具有特殊含义,用于定义更复杂的匹配规则。
3.2.1 字符类
| 元字符 | 描述 | 示例 |
|---|---|---|
. |
匹配除换行符以外的任意单个字符。 | /a.b/ 匹配 axb |
\d |
匹配任意一个数字 (0-9),等价于 [0-9]。 |
/\d+/ 匹配 123 |
\D |
匹配任意一个非数字字符,等价于 [^0-9]。 |
/\D/ 匹配 a |
\w |
匹配字母、数字或下划线,等价于 [A-Za-z0-9_]。 |
/\w/ 匹配 b |
\W |
匹配非字母、数字或下划线字符,等价于 [^A-Za-z0-9_]。 |
/\W/ 匹配 ! |
\s |
匹配任意一个空白字符(空格、制表符、换行符等)。 | /\s/ 匹配 |
\S |
匹配任意一个非空白字符。 | /\S/ 匹配 a |
[abc] |
匹配方括号中的任意一个字符。 | /[aeiou]/ 匹配 a |
[^abc] |
匹配不在方括号中的任意一个字符。 | /[^0-9]/ 匹配 a |
[a-z] |
匹配指定范围内的任意一个小写字母。 | /[a-z]/ 匹配 c |
[A-Z] |
匹配指定范围内的任意一个大写字母。 | /[A-Z]/ 匹配 D |
[0-9a-zA-Z] |
匹配数字或任意英文字母(无论大小写)。 | /[a-zA-Z0-9]/ |
[\u4e00-\u9fa5] |
匹配任意一个中文字符。 | /[\u4e00-\u9fa5]/ |
3.2.2 量词 (Quantifiers)
量词用于指定匹配前面字符或字符组的数量。
| 量词 | 描述 | 示例 |
|---|---|---|
* |
零次或多次,等价于 {0,}。 |
/a*/ 匹配 "", a, aaa |
+ |
一次或多次,等价于 {1,}。 |
/a+// 匹配 a, aaa |
? |
零次或一次,等价于 {0,1}。 |
/a?// 匹配 "", a |
{n} |
恰好 n 次。 | /a{3}/ 匹配 aaa |
{n,} |
至少 n 次。 | /a{2,}/ 匹配 aa, aaa |
{n,m} |
至少 n 次,但不超过 m 次。 | /a{1,3}/ 匹配 a, aa, aaa |
3.2.3 锚点 (Anchors)
锚点用于匹配字符串中的特定位置,而不是字符本身。
| 锚点 | 描述 |
|---|---|
^ |
匹配字符串的开头。在多行模式 (m 标志) 下,也匹配行的开头。 |
$ |
匹配字符串的结尾。在多行模式 (m 标志) 下,也匹配行的结尾。 |
\b |
匹配单词边界。 |
\B |
匹配非单词边界。 |
3.2.4 转义字符
\: 用于转义特殊字符,使其作为普通字符被匹配。例如,\. 匹配句点字符,而不是任意字符。
3.3 修饰符 (Flags)
修饰符用于指定全局匹配、区分大小写等匹配行为。
| 标志 | 描述 |
|---|---|
i |
(ignore case) 忽略大小写,进行不区分大小写的匹配。 |
g |
(global) 执行全局匹配,查找所有匹配项,而不是在找到第一个匹配后停止。 |
m |
(multiline) 执行多行匹配,使 ^ 和 $ 匹配行的开头和结尾,而不仅仅是整个字符串的开头和结尾。 |
4. 高级概念
4.1 分组与捕获
-
()(捕获组): 将多个字符组合成一个单元,并捕获匹配的子字符串以供后续使用(例如,后向引用或在match方法的结果中)。javascript
const re = /(ab)+/; // 匹配 "ab" 或 "abab" 等 -
(?:...)(非捕获组): 仅用于分组,不捕获匹配的子字符串,可以提高性能。javascript
const re = /(?:ab)+/; // 匹配 "ab" 或 "abab" 等,但不捕获 "ab"
4.2 后向引用 (Backreferences)
\n: 引用前面第 n 个捕获组匹配到的内容。
javascript
const re = /(\w)\1/; // 匹配连续两个相同的单词字符,如 "ee"
4.3 或 (Alternation)
|: 匹配 | 符号前或后的任何一个表达式。
javascript
const re = /(cat|dog)/; // 匹配 "cat" 或 "dog"
4.4 零宽度断言 (Lookarounds)
零宽度断言用于在匹配过程中指定一个位置,而不是实际匹配的内容。
-
(?=...)(正向肯定预查/Lookahead): 匹配后面跟着特定模式的x。javascript
const re = /foo(?=bar)/; // 匹配 "foobar" 中的 "foo",但前提是 "foo" 后面紧跟着 "bar" -
(?!...)(正向否定预查/Negative Lookahead): 匹配后面不跟着特定模式的x。javascript
const re = /foo(?!bar)/; // 匹配 "foobaz" 中的 "foo",但前提是 "foo" 后面不紧跟着 "bar" -
(?<=...)(反向肯定预查/Lookbehind): 匹配前面是特定模式的x。(ES2018+ 支持)javascript
const re = /(?<=foo)bar/; // 匹配 "foobar" 中的 "bar",但前提是 "bar" 前面紧跟着 "foo" -
(?<!...)(反向否定预查/Negative Lookbehind): 匹配前面不是特定模式的x。(ES2018+ 支持)javascript
const re = /(?<!foo)bar/; // 匹配 "bazbar" 中的 "bar",但前提是 "bar" 前面不紧跟着 "foo"
4.5 贪婪与惰性匹配
- 贪婪匹配 (Greedy): 量词默认是贪婪的,会尽可能多地匹配字符。
javascript
/a+/.exec("aaaaa") // 匹配 "aaaaa" - 惰性匹配 (Lazy): 在量词后加上
?可以使其变为惰性匹配,尽可能少地匹配字符。
javascript
/a+?/.exec("aaaaa") // 匹配 "a"
惰性量词包括:*?,+?,??,{n,}?,{n,m}?。
5. JavaScript 中与正则表达式相关的方法
JavaScript 提供了 RegExp 对象和 String 对象上的多种方法来使用正则表达式。
5.1 RegExp 对象的方法
-
test(string): 检测字符串是否匹配某个模式,如果匹配则返回true,否则返回false。javascript
const regex = /hello/;
console.log(regex.test("hello world")); // true -
exec(string): 在字符串中执行匹配搜索。如果找到匹配,则返回一个包含匹配信息(包括捕获组、索引等)的数组,并更新正则表达式的lastIndex属性;否则返回null。当使用g标志时,多次调用exec可以迭代所有匹配项。javascript
const regex = /world/g;
const str = "hello world, beautiful world";
let match1 = regex.exec(str); // ["world", index: 6, input: "...", groups: undefined]
let match2 = regex.exec(str); // ["world", index: 21, input: "...", groups: undefined]
5.2 String 对象的方法
-
match(regexp): 在字符串中查找一个或多个正则表达式的匹配。如果正则表达式带有g标志,则返回所有匹配项的数组;否则返回第一个匹配及其捕获组的数组。javascript
"hello world".match(/o/g); // ["o", "o"]
"hello world".match(/o/); // ["o", index: 4, input: "hello world", groups: undefined] -
matchAll(regexp): 返回一个迭代器,包含字符串中所有匹配正则表达式的结果,包括捕获组。它总是执行全局匹配,即使正则表达式没有g标志。javascript
const str = "test1test2";
const regex = /test(\d)/g;
for (const match of str.matchAll(regex)) {
console.log(match); // ["test1", "1", index: 0, ...] 和 ["test2", "2", index: 5, ...]
} -
search(regexp): 检索字符串中与正则表达式相匹配的子字符串的起始索引。如果没有找到任何匹配,则返回-1。javascript
"hello world".search(/world/); // 6
"hello world".search(/foo/); // -1 -
replace(regexp|substr, newSubstr|function): 使用替换字符串或函数替换与正则表达式匹配的子字符串。如果正则表达式没有g标志,则只替换第一个匹配项。javascript
"hello world".replace(/world/, "JavaScript"); // "hello JavaScript"
"foo bar foo".replace(/foo/g, "baz"); // "baz bar baz" -
split(regexp|separator, limit): 使用正则表达式或固定字符串作为分隔符,将一个字符串分割成一个字符串数组。javascript
"1,2,3".split(/,/); // ["1", "2", "3"]
"apple;banana,orange".split(/[;,]/); // ["apple", "banana", "orange"]
6. 前端开发中的实际应用
正则表达式在前端开发中有着广泛的应用,尤其是在表单验证和文本处理方面。
-
表单验证:
- 邮箱地址:
^\S+@\S+\.\S+$ - 手机号码:
^1[3-9]\d{9}$(中国大陆简化版) - URL:
^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$ - 日期 (YYYY-MM-DD):
^\d{4}(\-)\d{1,2}\1\d{1,2}$ - 密码强度: 例如,匹配至少包含一个大写字母、一个小写字母和一个数字,且长度至少为8位:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$
- 邮箱地址:
-
文本搜索与替换:
- 高亮显示搜索结果。
- 动态格式化用户输入,例如在输入时自动添加分隔符。
- 清理或转换用户输入的特殊字符。
-
数据提取:
- 从 URL 查询参数中提取特定值。
- 从 HTML 字符串中提取标签属性或内容。
- 解析日志或特定格式的文本数据。
7. 技巧与最佳实践
- 从小处着手: 逐步构建复杂的正则表达式,先匹配核心部分,再添加修饰符和高级特性。
- 使用在线工具: 利用在线正则表达式测试工具(如 Regex101、RegExr)进行实时测试和调试,它们通常提供详细的解释和匹配过程可视化。
- 注释: 对于复杂的正则表达式,添加注释说明其意图,或将其分解为可读性更高的部分。
- 性能考虑:
- 避免过度复杂的正则表达式,尤其是在处理大量文本时,可能会导致“回溯失控”(catastrophic backtracking),严重影响性能。
- 在不需要捕获组时,优先使用非捕获组
(?:...)。 - 当匹配固定字符串时,优先使用
String.prototype.includes()或indexOf(),它们通常比正则表达式更快。
- 惰性匹配: 在需要时使用惰性匹配
?来避免不必要的贪婪匹配,这对于解析标签对(如<tag>...</tag>) 等场景非常有用。 - 利用
RegExp构造函数处理动态模式: 当模式字符串中包含变量时,务必使用new RegExp()来构建正则表达式,而不是字面量。
掌握 JavaScript 正则表达式将极大地提升前端开发者处理字符串的能力,是成为一名高效前端工程师的必备技能。通过不断实践和学习,你将能够灵活运用正则表达式解决各种文本处理难题。