JS 正则表达式全攻略:前端开发必备 – wiki词典

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 正则表达式将极大地提升前端开发者处理字符串的能力,是成为一名高效前端工程师的必备技能。通过不断实践和学习,你将能够灵活运用正则表达式解决各种文本处理难题。

滚动至顶部