好的,作为{{IDENTITY}},我将为您呈现一份关于 JavaScript 中**正则表达式(Regular Expressions,简称 Regex 或 RegExp)**的完整参考指南。这份文档将涵盖正则表达式的基础概念、创建方法、核心语法、常用操作方法,以及在实际开发中的应用场景和最佳实践,旨在提供全面、高质量、富有教育价值的内容。


在 JavaScript 开发中,处理字符串是日常任务。无论是验证用户输入(如邮箱、电话号码)、从文本中提取特定信息(如 URL、日期),还是进行复杂的字符串查找和替换,常规的字符串方法往往力不从心。这时,**正则表达式(Regular Expressions,简称 Regex 或 RegExp)**便成为了我们手中的强大工具。

正则表达式是一种强大的模式匹配语言,它允许您用简洁而富有表现力的语法来定义字符串的搜索模式。掌握正则表达式,将极大地提升您处理文本数据的能力,使您的代码更加高效、灵活和健壮。

本指南将带您全面深入地了解 JavaScript 中的正则表达式,从最基本的概念到高级特性,助您解锁字符串操作的无限可能。


前置知识

为了更好地理解本指南内容,建议您具备以下 JavaScript 基础知识:

  • JavaScript 基础: 熟悉字符串、函数、数组、对象等基本数据类型和结构。
  • 基本字符串操作:String.prototype 上的一些基本方法(如 indexOf, slice 等)有所了解。

目录

  1. 什么是正则表达式?
    • 1.1 定义与核心用途
    • 1.2 为什么需要正则表达式?
  2. 创建正则表达式
    • 2.1 字面量语法 (Literal Syntax)
    • 2.2 RegExp 构造函数 (Constructor)
    • 2.3 选择哪种创建方式?
  3. 正则表达式标志 (Flags)
    • 3.1 g (Global)
    • 3.2 i (Case-Insensitive)
    • 3.3 m (Multiline)
    • 3.4 u (Unicode)
    • 3.5 y (Sticky)
    • 3.6 s (DotAll)
  4. 正则表达式模式的核心元素
    • 4.1 普通字符 (Literal Characters)
    • 4.2 特殊字符 (Special Characters) / 元字符 (Metacharacters)
      • 4.2.1 字符类 (Character Classes)
        • . (点号)
        • \d, \D (数字/非数字)
        • \w, \W (字母数字下划线/非字母数字下划线)
        • \s, \S (空白符/非空白符)
      • 4.2.2 字符集 (Character Sets) []
        • 范围 [a-z]
        • 排除 [^abc]
      • 4.2.3 量词 (Quantifiers)
        • * (零次或多次)
        • + (一次或多次)
        • ? (零次或一次)
        • {n} (恰好 n 次)
        • {n,} (至少 n 次)
        • {n,m} (n 到 m 次)
        • 贪婪模式 (Greedy) vs. 非贪婪模式 (Lazy)
      • 4.2.4 定位符 (Anchors)
        • ^ (开头)
        • $ (结尾)
        • \b, \B (单词边界/非单词边界)
      • 4.2.5 分组与捕获 (Grouping and Capturing) ()
        • 捕获分组
        • 非捕获分组 (?:)
        • 反向引用 (Backreferences) \n
        • 命名捕获分组 (?<name>...)
      • 4.2.6 选择 (Alternation) |
      • 4.2.7 转义字符 \
      • 4.2.8 预查 (Lookahead and Lookbehind) (高级)
        • 正向肯定预查 (?=...)
        • 正向否定预查 (?!...)
        • 反向肯定预查 (?<=...) (ES2018)
        • 反向否定预查 (?<!...) (ES2018)
  5. RegExp 对象的方法
    • 5.1 regex.test(string)
    • 5.2 regex.exec(string)
  6. String 对象中与正则表达式相关的方法
    • 6.1 string.match(regex)
    • 6.2 string.matchAll(regex) (ES2020)
    • 6.3 string.search(regex)
    • 6.4 string.replace(regex, replacement)
    • 6.5 string.replaceAll(regex, replacement) (ES2021)
    • 6.6 string.split(regex)
  7. 实际应用示例
    • 7.1 邮箱地址验证
    • 7.2 URL 提取
    • 7.3 日期格式转换
    • 7.4 密码强度检查
    • 7.5 HTML 标签替换 (简单示例)
  8. 性能考量与最佳实践
    • 8.1 避免回溯失控 (Catastrophic Backtracking)
    • 8.2 预编译正则表达式
    • 8.3 使用非捕获分组 (?:)
    • 8.4 谨慎使用 .*
    • 8.5 正则表达式调试工具
  9. 总结
  10. 附录:常用正则表达式速查表

1. 什么是正则表达式?

1.1 定义与核心用途

正则表达式 (Regular Expression) 是一种由字符和特殊符号组成的模式,用于描述、匹配、查找和操作字符串中特定序列的字符。简单来说,它就像是字符串的“高级搜索与替换”工具。

其核心用途包括:

  • 搜索 (Search): 在一个长字符串中查找是否包含某个特定模式的子字符串。
  • 匹配 (Match): 检查整个字符串是否符合某个模式。
  • 提取 (Extract): 从字符串中抽取出符合特定模式的片段。
  • 替换 (Replace): 将字符串中符合某个模式的片段替换成其他内容。
  • 验证 (Validate): 检查用户输入(如邮箱、密码、电话号码)是否符合预设的格式规则。
1.2 为什么需要正则表达式?

考虑以下场景:

  • 您需要找出所有以 http://https:// 开头的网址。
  • 您需要验证用户输入的电话号码是否为 11 位数字。
  • 您需要把文章中的所有 “color” 替换为 “colour”。

如果只使用 String.prototype 上的方法(如 indexOf, startsWith, replace),这些任务将变得非常复杂甚至无法完成。正则表达式提供了一种简洁而强大的声明式方式来解决这些问题。


2. 创建正则表达式

在 JavaScript 中,有两种主要的方式来创建正则表达式。

2.1 字面量语法 (Literal Syntax)

这是最常见和推荐的方式,当您的正则表达式模式是静态且已知时使用。

语法: /pattern/flags

  • pattern: 要匹配的模式。
  • flags: 可选,用于改变匹配行为的标志(如 g, i, m 等)。

示例:

const regex1 = /hello/;        // 匹配字符串中的 "hello"
const regex2 = /world/i;       // 匹配 "world",忽略大小写 (World, WORLD, wOrLd 都能匹配)
const regex3 = /[0-9]+/g;      // 匹配一个或多个数字,全局匹配

特点:

  • 在代码加载时编译一次,效率更高。
  • 不能使用变量来构建模式或标志。
2.2 RegExp 构造函数 (Constructor)

当正则表达式的模式或标志需要根据变量动态生成时,使用 RegExp 构造函数。

语法: new RegExp('pattern', 'flags')

  • 'pattern': 要匹配的模式字符串。
  • 'flags': 可选,用于改变匹配行为的标志字符串。

重要提示: 在字符串中表示正则表达式时,任何特殊字符(例如 \)都需要进行双重转义。因为字符串本身会先处理一次转义。

示例:

const searchTerm = "hello";
const dynamicRegex = new RegExp(searchTerm); // 动态创建 /hello/

console.log(dynamicRegex.test("say hello world")); // true

const numberPattern = "\\d+"; // 在字符串中,`\` 需要转义为 `\\`
const dynamicRegexWithFlag = new RegExp(numberPattern, 'g');
console.log(dynamicRegexWithFlag.test("123 test 456")); // true

// 错误的示例:没有双重转义
// const wrongRegex = new RegExp("\d+"); // 错误!`\d` 在字符串中会被解释为 'd'
// console.log(wrongRegex.test("123")); // false
2.3 选择哪种创建方式?
  • 字面量语法 /pattern/flags 当正则表达式模式和标志在编写代码时是固定不变的,推荐使用字面量语法,因为它更简洁、可读性更好,并且在代码加载时就被编译,性能通常更高。
  • RegExp 构造函数 new RegExp('pattern', 'flags') 当正则表达式模式或标志需要根据运行时数据(例如用户输入、配置变量)动态生成时,必须使用构造函数。请记住字符串中的特殊字符需要进行双重转义。

3. 正则表达式标志 (Flags)

标志是修饰符,可以改变正则表达式的匹配行为。它们通常放在正则表达式的末尾。

3.1 g (Global)
  • 含义: 全局匹配。找到所有匹配,而不是在找到第一个匹配后停止。
  • 默认: 如果没有 g 标志,只会匹配一次。
  • 示例:
const text = "apple banana apple orange";
const regex = /apple/g;
let match;
while ((match = regex.exec(text)) !== null) {
  console.log(`Found ${match[0]} at index ${match.index}`);
}
// 输出:
// Found apple at index 0
// Found apple at index 13
3.2 i (Case-Insensitive)
  • 含义: 忽略大小写。匹配时不区分大小写。
  • 示例:
const text = "Hello World";
const regex = /hello/i;
console.log(regex.test(text)); // true
3.3 m (Multiline)
  • 含义: 多行模式。^$ 不再只匹配整个字符串的开头和结尾,而是匹配每行的开头和结尾(由换行符 \n 或回车符 \r 分隔)。
  • 示例:
const text = "Line 1\nLine 2\nLine 3";
const regexWithoutM = /^Line 2/;
console.log(regexWithoutM.test(text)); // false (因为 Line 2 不是整个字符串的开头)

const regexWithM = /^Line 2/m;
console.log(regexWithM.test(text)); // true (因为 Line 2 是第二行的开头)
3.4 u (Unicode) (ES6)
  • 含义: Unicode 模式。启用完整的 Unicode 支持,例如处理代理对(Surrogate Pairs)和 Unicode 属性转义。
  • 示例:
const text = "𝄐"; // 一个 Unicode 代理对字符
const regex = /./; // 默认情况下,. 可能无法正确处理代理对
console.log(regex.test(text)); // true (但某些老引擎可能错误)
console.log(text.match(regex)[0].length); // 2 (字符长度,而非 Unicode 码点数)

const regexU = /./u;
console.log(regexU.test(text)); // true
console.log(text.match(regexU)[0].length); // 2 (依然是JS字符串的UTF-16码元长度)

// u 标志主要影响 \p{} (Unicode 属性转义) 和 {n} 量词对码点的计数
// 示例 (需要Node.js 10+ 或支持 \p 的浏览器):
// const emojiRegex = /\p{Emoji}/u; // 匹配任何 Emoji 字符
// console.log(emojiRegex.test("😄")); // true
3.5 y (Sticky) (ES6)
  • 含义: 粘性匹配。要求匹配必须从字符串的 lastIndex 属性指定的位置开始。如果 lastIndex 处的第一个字符不匹配,则不进行任何匹配。
  • 示例:
const text = "abc_abc_abc";
const regexY = /abc/gy;

console.log(regexY.exec(text)); // ["abc", index: 0, ...] lastIndex = 3
console.log(regexY.exec(text)); // null (因为下一个字符是 '_', 不匹配 'abc')

regexY.lastIndex = 4; // 手动设置从 index 4 开始
console.log(regexY.exec(text)); // ["abc", index: 4, ...] lastIndex = 7
3.6 s (DotAll) (ES9)
  • 含义: dotAll 模式。使 . 元字符匹配包括换行符在内的任何单个字符。
  • 默认: . 不匹配换行符 (\n), 回车符 (\r), 行分隔符 (\u2028), 段落分隔符 (\u2029)。
  • 示例:
const text = "Hello\nWorld";
const regexWithoutS = /Hello.World/;
console.log(regexWithoutS.test(text)); // false

const regexWithS = /Hello.World/s;
console.log(regexWithS.test(text)); // true

4. 正则表达式模式的核心元素

正则表达式模式由普通字符和特殊字符(元字符)组合而成。

4.1 普通字符 (Literal Characters)

任何非特殊字符都会匹配其自身。

  • a, b, c, 1, 2, 3 等。
  • 示例: /cat/ 匹配字符串中的 “cat”。
4.2 特殊字符 (Special Characters) / 元字符 (Metacharacters)

元字符是具有特殊含义的字符,用于构建更复杂的匹配模式。

4.2.1 字符类 (Character Classes)

预定义的字符集,代表一类字符。

  • . (点号):

    • 匹配除换行符 (\n), 回车符 (\r), 行分隔符 (\u2028), 段落分隔符 (\u2029) 之外的任何单个字符
    • 如果使用了 s (dotAll) 标志,则匹配包括换行符在内的任何单个字符
    • 示例: /a.c/ 匹配 “abc”, “adc”, “aXc” 等。
  • \d (digit):

    • 匹配任何数字字符 (0-9)。等同于 [0-9]
    • 示例: /\d{3}/ 匹配连续三个数字,如 “123”。
  • \D (non-digit):

    • 匹配任何非数字字符。等同于 [^0-9]
    • 示例: /\D+/ 匹配一个或多个非数字字符。
  • \w (word character):

    • 匹配任何字母数字字符或下划线 (a-z, A-Z, 0-9, _)。
    • 示例: /\w+/ 匹配一个或多个单词字符。
  • \W (non-word character):

    • 匹配任何非字母数字或非下划线字符
    • 示例: /\W+/ 匹配一个或多个非单词字符(如空格、标点符号)。
  • \s (whitespace):

    • 匹配任何空白字符(空格、制表符 \t、换行符 \n、回车符 \r、换页符 \f、垂直制表符 \v、Unicode 空白符等)。
    • 示例: /hello\s+world/ 匹配 “hello world” (中间有一个或多个空格)。
  • \S (non-whitespace):

    • 匹配任何非空白字符
    • 示例: /\S+/ 匹配一个或多个非空白字符。
4.2.2 字符集 (Character Sets) []

方括号 [] 定义一个字符集,匹配其中任意一个字符

  • [abc]: 匹配 “a”, “b”, 或 “c” 中的任意一个。
  • [a-z]: 匹配从小写 “a” 到小写 “z” 范围内的任意一个字符。
  • [A-Z]: 匹配从大写 “A” 到大写 “Z” 范围内的任意一个字符。
  • [0-9]: 匹配从 “0” 到 “9” 范围内的任意一个数字。
  • [a-zA-Z0-9]: 匹配任何字母或数字。
  • [^abc]: 匹配除了 “a”, “b”, “c” 之外的任意一个字符(^[] 内部表示否定)。

示例:

const text = "color colour";
const regex = /colou?r/; // 匹配 "color" 或 "colour"
console.log(text.match(regex)); // ["color", ...]

const text2 = "123-abc-456";
const regex2 = /[0-9A-Za-z-]+/g; // 匹配数字、字母或连字符
console.log(text2.match(regex2)); // ["123-abc-", "456"]
4.2.3 量词 (Quantifiers)

量词指定了模式中某个部分可以出现的次数。

  • * (星号): 匹配前一个元素零次或多次

    • 示例: /ab*c/ 匹配 “ac”, “abc”, “abbc”, “abbbc” 等。
  • + (加号): 匹配前一个元素一次或多次

    • 示例: /ab+c/ 匹配 “abc”, “abbc”, “abbbc” 等,但不匹配 “ac”。
  • ? (问号): 匹配前一个元素零次或一次(使其可选)。

    • 示例: /colou?r/ 匹配 “color” 或 “colour”。
  • {n} (花括号): 匹配前一个元素恰好 n 次

    • 示例: /\d{3}/ 匹配连续三个数字,如 “123”。
  • {n,}: 匹配前一个元素至少 n 次

    • 示例: /\d{3,}/ 匹配至少三个数字,如 “123”, “1234”。
  • {n,m}: 匹配前一个元素n 到 m 次

    • 示例: /\d{3,5}/ 匹配三到五个数字。
  • 贪婪模式 (Greedy) vs. 非贪婪模式 (Lazy):

    • 默认: 量词是贪婪的,会尽可能多地匹配字符。
    • 非贪婪: 在量词后面加上 ?,使其变为非贪婪模式,会尽可能少地匹配字符。
    • 示例:
const html = "<div><span>hello</span><span>world</span></div>";

// 贪婪模式:尽可能多地匹配
const greedyRegex = /<div>.*<\/div>/;
console.log(html.match(greedyRegex)[0]);
// 输出: <div><span>hello</span><span>world</span></div> (匹配了整个字符串)

// 非贪婪模式:尽可能少地匹配
const lazyRegex = /<div>.*?<\/div>/;
console.log(lazyRegex.exec(html)[0]);
// 输出: <div><span>hello</span><span>world</span></div> (这里因为只有一个 div 标签,所以结果一样)

const spanLazyRegex = /<span>.*?<\/span>/g;
console.log(html.match(spanLazyRegex));
// 输出: ["<span>hello</span>", "<span>world</span>"] (每个 span 标签都被独立匹配)
4.2.4 定位符 (Anchors)

定位符不匹配实际字符,而是匹配字符串中的特定位置。

  • ^ (脱字符):

    • 默认情况下,匹配字符串的开头
    • 如果使用 m (multiline) 标志,则匹配每行的开头
    • 示例: /^hello/ 匹配 “hello world”,但不匹配 “say hello”。
  • $ (美元符号):

    • 默认情况下,匹配字符串的结尾
    • 如果使用 m (multiline) 标志,则匹配每行的结尾
    • 示例: /world$/ 匹配 “hello world”,但不匹配 “world wide”。
  • \b (word boundary):

    • 匹配一个单词边界。单词边界是指一个 \w 字符和一个 \W 字符之间的位置,或者字符串的开头/结尾和一个 \w 字符之间的位置。
    • 示例: /\bcat\b/ 匹配 “The cat sat.” 中的 “cat”,但不匹配 “category” 或 “tomcat”。
  • \B (non-word boundary):

    • 匹配一个非单词边界
    • 示例: /\Bcat\B/ 匹配 “tomcat” 中的 “cat”,但不匹配 “The cat sat.”。
4.2.5 分组与捕获 (Grouping and Capturing) ()

圆括号 () 用于将多个字符组合成一个逻辑单元,并且可以捕获匹配的子字符串。

  • 捕获分组:

    • 使用 () 括起来的模式会创建一个捕获分组。
    • 匹配到的内容会作为 exec() 方法返回数组或 match() 方法返回数组的附加元素。
    • 示例: /(ab)+/ 匹配 “abab” 中的 “ab”。
  • 非捕获分组 (?:):

    • 使用 (?:) 括起来的模式会进行分组,但不捕获匹配的内容。
    • 优点: 提高性能,减少返回数组的复杂度,当您只需要分组而不需要捕获时使用。
    • 示例: (?:ab)+ 匹配 “abab” 中的 “ab”,但不会在结果数组中额外捕获。
  • 反向引用 (Backreferences) \n:

    • \n (其中 n 是一个数字,如 \1, \2) 用于引用之前捕获分组所匹配的内容。
    • 示例: /(\w)\1/ 匹配任何两个连续相同的字母,如 “oo”, “ee”, “gg”。
  • 命名捕获分组 (?<name>...) (ES2018):

    • 允许您为捕获分组指定一个名称,而不是使用数字索引。
    • exec() 结果的 groups 属性中通过名称访问。
    • 示例: /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const dateString = "2023-10-27";
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = dateRegex.exec(dateString);

console.log(match.groups.year);  // 2023
console.log(match.groups.month); // 10
console.log(match.groups.day);   // 27
4.2.6 选择 (Alternation) |

竖线 | 作为 “或” 运算符,匹配其两侧的任意一个模式。

  • 示例: /cat|dog/ 匹配 “cat” 或 “dog”。
  • 示例: /gr(a|e)y/ 匹配 “gray” 或 “grey”。
4.2.7 转义字符 \

反斜杠 \ 用于转义特殊字符,使其失去特殊含义,作为普通字符进行匹配。

  • 如果您想匹配字面量的 .*+?|{}()[]^$\,您需要在它们前面加上 \
  • 示例: /\./ 匹配一个字面量的点号。
  • 示例: /\\/ 匹配一个字面量的反斜杠。
4.2.8 预查 (Lookahead and Lookbehind) (高级)

预查是一种零宽度的断言(zero-width assertion),它们不消耗字符,只是检查匹配位置的右侧(lookahead)或左侧(lookbehind)是否存在或不存在某个模式。

  • 正向肯定预查 (?=...) (Positive Lookahead):

    • 匹配后面紧跟着指定模式的字符串。
    • 示例: /\w+(?=\s*美元)/ 匹配后面跟着 " 美元" 的单词,但不包括 " 美元" 本身。
      const price = "我付了50美元";
      const regex = /\d+(?=\s*美元)/;
      console.log(price.match(regex)[0]); // 50
      
  • 正向否定预查 (?!...) (Negative Lookahead):

    • 匹配后面紧跟着指定模式的字符串。
    • 示例: /q(?!u)/ 匹配后面不跟着 “u” 的 “q”。
      const words = "queen question Iraq quiche";
      const regex = /q(?!u)/g;
      console.log(words.match(regex)); // ["q"] (匹配 Iraq 中的 'q')
      
  • 反向肯定预查 (?<=...) (Positive Lookbehind) (ES2018):

    • 匹配前面紧跟着指定模式的字符串。
    • 示例: /(?<=¥)\d+/ 匹配前面跟着 “¥” 的数字。
      const currency = "¥100 $200";
      const regex = /(?<=¥)\d+/;
      console.log(currency.match(regex)[0]); // 100
      
  • 反向否定预查 (?<!...) (Negative Lookbehind) (ES2018):

    • 匹配前面紧跟着指定模式的字符串。
    • 示例: /(?<!http:)\/\// 匹配前面不是 “http:” 的 //
      const urls = "http://example.com //comment";
      const regex = /(?<!http:)\/\//;
      console.log(urls.match(regex)[0]); // // (匹配 comment 前的 // )
      

5. RegExp 对象的方法

正则表达式本身 (RegExp 对象) 也提供了一些方法。

5.1 regex.test(string)
  • 作用: 检查字符串中是否存在匹配模式的子字符串。
  • 返回值: 如果找到匹配,返回 true;否则返回 false
  • 副作用: 如果正则表达式带有 g 标志,test() 会更新 regex.lastIndex 属性。
  • 示例:
const regex = /hello/i;
console.log(regex.test("Hello World")); // true
console.log(regex.test("hi there"));    // false
5.2 regex.exec(string)
  • 作用: 在字符串中执行一次搜索,查找匹配项。
  • 返回值:
    • 如果找到匹配,返回一个数组,包含匹配的详细信息。
    • 如果未找到,返回 null
  • 数组结构:
    • [0]: 完整的匹配字符串。
    • [1], [2], …: 捕获分组的匹配项。
    • index: 匹配项在原字符串中的起始索引。
    • input: 原始字符串。
    • groups: (ES2018) 一个对象,包含命名捕获分组的匹配项。
  • 副作用: 如果正则表达式带有 g 标志,exec() 会更新 regex.lastIndex 属性,允许您循环查找所有匹配。
  • 示例:
const text = "The cat sat on the mat.";
const regex = /the (\w+)/g; // 带有全局标志和捕获分组

let match1 = regex.exec(text);
console.log(match1);
// 输出: ["the cat", "cat", index: 4, input: "The cat sat on the mat.", groups: undefined]
console.log(regex.lastIndex); // 11

let match2 = regex.exec(text);
console.log(match2);
// 输出: ["the mat", "mat", index: 17, input: "The cat sat on the mat.", groups: undefined]
console.log(regex.lastIndex); // 24

let match3 = regex.exec(text);
console.log(match3); // null

循环查找所有匹配 (配合 g 标志):

const article = "Email: test@example.com. Contact: info@domain.org.";
const emailRegex = /(\w+)@(\w+\.\w+)/g; // 捕获用户名和域名

let match;
while ((match = emailRegex.exec(article)) !== null) {
  console.log(`完整匹配: ${match[0]}, 用户名: ${match[1]}, 域名: ${match[2]}`);
}
// 输出:
// 完整匹配: test@example.com, 用户名: test, 域名: example.com
// 完整匹配: info@domain.org, 用户名: info, 域名: domain.org

6. String 对象中与正则表达式相关的方法

JavaScript 的 String.prototype 上也提供了一些可以直接接受正则表达式作为参数的方法。

6.1 string.match(regex)
  • 作用: 检查字符串中是否有匹配项。
  • 返回值:
    • 如果 regex 没有 g 标志,返回一个与 regex.exec() 结果类似的数组(第一个匹配的完整信息),或 null
    • 如果 regex 带有 g 标志,返回一个包含所有完整匹配字符串的数组,如果没有匹配则返回 null
  • 示例:
const sentence = "I have 2 apples and 3 bananas.";

// 无 g 标志
console.log(sentence.match(/\d/));
// ["2", index: 9, input: "I have 2 apples and 3 bananas.", groups: undefined]

// 有 g 标志
console.log(sentence.match(/\d/g));  // ["2", "3"]

// 有 g 标志和捕获分组
console.log(sentence.match(/(\d)\s(\w+)/g)); // ["2 apples", "3 bananas"] (只会返回完整匹配,捕获分组信息会丢失)

注意: 如果 match()g 标志一起使用,并且正则表达式包含捕获分组,则返回的数组将只包含所有完整匹配的字符串,而不会包含捕获分组的详细信息。要获取所有完整匹配及其捕获分组的详细信息,请使用 matchAll()

6.2 string.matchAll(regex) (ES2020)
  • 作用: 返回一个迭代器,包含所有匹配项的详细信息(包括捕获分组),类似于 regex.exec() 的结果。
  • 要求: regex 必须带有 g 标志。
  • 示例:
const text = "Emails: user1@example.com, user2@domain.org.";
const emailRegex = /(\w+)@(\w+\.\w+)/g;

for (const match of text.matchAll(emailRegex)) {
  console.log(`完整匹配: ${match[0]}, 用户名: ${match[1]}, 域名: ${match[2]}`);
}
// 输出:
// 完整匹配: user1@example.com, 用户名: user1, 域名: example.com
// 完整匹配: user2@domain.org, 用户名: user2, 域名: domain.org
6.3 string.search(regex)
  • 作用: 查找字符串中第一个匹配项的索引
  • 返回值: 第一个匹配项的起始索引,如果没有找到则返回 -1
  • 特点: 忽略 g 标志。
  • 示例:
const sentence = "The quick brown fox.";
console.log(sentence.search(/quick/)); // 4
console.log(sentence.search(/lazy/));  // -1
6.4 string.replace(regex, replacement)
  • 作用: 替换字符串中与模式匹配的部分。

  • 返回值: 替换后的新字符串。

  • 参数:

    • regex: 要搜索的正则表达式。
    • replacement: 替换字符串或一个函数。
  • 特点:

    • 如果 regex 没有 g 标志,只替换第一个匹配项。
    • 如果 regex 带有 g 标志,替换所有匹配项。
  • 替换字符串中的特殊 $n 序列:

    • $&: 匹配的整个子字符串。
    • $ (或 $): 第 n 个捕获分组匹配的子字符串。
    • $` : 匹配发生位置前的子字符串。
    • $': 匹配发生位置后的子字符串。
    • $$: 字面量的 $.
    • $<name>: 命名捕获分组的匹配值。
  • replacement 是函数: 如果 replacement 是一个函数,它会为每个匹配项执行,其返回值作为替换值。

    • 函数参数: (match, p1, p2, ..., offset, string, groups)
      • match: 匹配的整个子字符串。
      • p1, p2, ...: 捕获分组的匹配项。
      • offset: 匹配项在原字符串中的起始索引。
      • string: 原始字符串。
      • groups: (ES2018) 命名捕获分组的对象。
  • 示例 (替换字符串):

const text = "Hello World! world hello.";

// 替换第一个 "world"
console.log(text.replace(/world/, "宇宙")); // Hello 宇宙! world hello.

// 替换所有 "hello" (忽略大小写)
console.log(text.replace(/hello/gi, "你好")); // 你好 World! world 你好.

// 使用捕获分组反向引用
console.log("Name: John Doe".replace(/(\w+)\s(\w+)/, "$2, $1")); // Name: Doe, John

// 使用命名捕获分组 (ES2018)
const date = "日期: 2023-10-27";
const formattedDate = date.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, "$<month>/$<day>/$<year>");
console.log(formattedDate); // 日期: 10/27/2023
  • 示例 (replacement 是函数):
const str = "apple_10 banana_20 orange_5";
// 将所有价格翻倍
const result = str.replace(/(\w+)_(\d+)/g, (match, fruit, price) => {
  return `${fruit}_${parseInt(price) * 2}`;
});
console.log(result); // apple_20 banana_40 orange_10
6.5 string.replaceAll(regex, replacement) (ES2021)
  • 作用: 替换字符串中所有与模式匹配的部分。
  • 要求: regex 必须带有 g 标志。
  • 返回值: 替换后的新字符串。
  • 参数和替换字符串的特殊序列:string.replace() 相同。
  • 示例:
const text = "apple pie, apple juice";

// 没有 g 标志会报错!
// console.log(text.replaceAll(/apple/, "orange")); // TypeError

// 必须带 g 标志
console.log(text.replaceAll(/apple/g, "orange")); // orange pie, orange juice
6.6 string.split(regex)
  • 作用: 使用正则表达式作为分隔符,将字符串分割成一个字符串数组
  • 返回值: 字符串数组。
  • 示例:
const data = "item1, item2; item3 | item4";
// 使用逗号、分号或竖线作为分隔符
console.log(data.split(/[,;|]\s*/)); // ["item1", "item2", "item3", "item4"]

const sentence = "Hello   World\nHow are you?";
// 使用一个或多个空白字符作为分隔符
console.log(sentence.split(/\s+/)); // ["Hello", "World", "How", "are", "you?"]

7. 实际应用示例

7.1 邮箱地址验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

console.log(emailRegex.test("test@example.com"));    // true
console.log(emailRegex.test("invalid-email"));       // false
console.log(emailRegex.test("user.name@sub.domain.co.uk")); // true
7.2 URL 提取
const text = "Visit our website at https://www.example.com or http://sub.domain.net.";
const urlRegex = /https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\/[^"'\s]*)?/g;

console.log(text.match(urlRegex));
// 输出: ["https://www.example.com", "http://sub.domain.net"]
7.3 日期格式转换 (YYYY-MM-DD 到 MM/DD/YYYY)
const date = "今天的日期是 2023-10-27。";
const formattedDate = date.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(formattedDate); // 今天的日期是 10/27/2023。

或者使用命名捕获分组:

const date2 = "发货日期: 2024-01-01";
const formattedDate2 = date2.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/, "$<month>/$<day>/$<year>");
console.log(formattedDate2); // 发货日期: 01/01/2024
7.4 密码强度检查 (至少8位,包含大小写字母和数字)
const password = "Password123";
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;

console.log(passwordRegex.test(password));         // true
console.log(passwordRegex.test("password123"));    // false (缺少大写)
console.log(passwordRegex.test("PASSWORD123"));    // false (缺少小写)
console.log(passwordRegex.test("Pass1"));          // false (长度不足)
7.5 HTML 标签替换 (简单示例)

重要提示: 正则表达式不适合解析复杂的、嵌套的 HTML/XML 结构。对于这类任务,应使用 DOM 解析器。但对于简单的替换,正则可以胜任。

const htmlString = "<p>Hello <b>World</b>!</p>";
// 移除所有 HTML 标签
const plainText = htmlString.replace(/<[^>]*>/g, "");
console.log(plainText); // Hello World!

8. 性能考量与最佳实践

尽管正则表达式功能强大,但如果使用不当,也可能导致性能问题,甚至出现回溯失控 (Catastrophic Backtracking)

8.1 避免回溯失控 (Catastrophic Backtracking)

这是正则表达式中最常见的性能陷阱。当正则表达式中包含嵌套的重复分组,并且这些分组可能匹配空字符串,或者它们的匹配范围有重叠时,就可能发生。在没有匹配成功的情况下,正则表达式引擎会尝试所有可能的组合,导致指数级的回溯时间。

典型模式: (a+)+(ab|a)*c 匹配 aaaaaaaaaaaaab

// 极易导致回溯失控的示例
// const catastrophicRegex = /(a+)+b/; // 匹配 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaac" 会非常慢甚至崩溃
// try {
//     catastrophicRegex.test("aaaaaaaaaaaaaaaaaaaaaaaaaaaaac");
// } catch (e) {
//     console.error("Regex took too long or crashed:", e);
// }

// 改进后的示例,避免嵌套量词
const improvedRegex = /a+b/; // 避免嵌套重复
console.log(improvedRegex.test("aaab")); // true

避免方法:

  • 避免不必要的嵌套量词。
  • 使用非捕获分组 (?:...) 优化。
  • 使用更精确的字符集而不是 .
8.2 预编译正则表达式

如果您在循环中或函数内部多次使用相同的正则表达式,最好将其定义在循环或函数外部,这样它只需要编译一次。

// 差的实践
function countWordsBad(text) {
  let count = 0;
  for (let i = 0; i < text.length; i++) {
    if (/\b\w+\b/.test(text[i])) { // 每次迭代都重新编译
      count++;
    }
  }
  return count;
}

// 好的实践
const wordRegex = /\b\w+\b/g; // 预编译一次
function countWordsGood(text) {
  return (text.match(wordRegex) || []).length;
}
8.3 使用非捕获分组 (?:)

如果一个分组只是为了组织模式逻辑,而您不需要捕获其匹配的内容,请使用非捕获分组 (?:...)。这可以减少引擎的工作量,稍微提高性能。

// 捕获分组,可能会有额外开销
const regexWithCapture = /(foo|bar)\s(baz)/;

// 非捕获分组,更高效
const regexWithoutCapture = /(?:foo|bar)\s(baz)/;
8.4 谨慎使用 .*

. 匹配除了换行符外的任何字符,* 匹配零次或多次。它们的组合 .* 非常贪婪和宽泛,可能导致意想不到的匹配结果或回溯问题。尽量使用更具体的字符集和量词。

8.5 正则表达式调试工具

使用在线正则表达式测试工具(如 Regex101.com, RegExr.com)可以帮助您可视化匹配过程,理解回溯,并优化您的模式。


9. 总结

正则表达式是 JavaScript 中处理字符串的强大工具,它允许我们用简洁的模式语言完成复杂的搜索、匹配、提取和替换任务。

  • 创建: 可以使用字面量语法 /pattern/flags (静态模式) 或 RegExp 构造函数 new RegExp('pattern', 'flags') (动态模式)。
  • 标志: g (全局), i (忽略大小写), m (多行), u (Unicode), y (粘性), s (点匹配所有)。
  • 模式: 由普通字符、字符类 (\d, \w, \s, . 等), 字符集 ([]), 量词 (*, +, ?, {}), 定位符 (^, $, \b), 分组 ((), (?:), (?<name>...)), 选择 (|), 转义 (\) 和预查 ((?=), (?!), (?<=), (?<!)) 组成。
  • 方法: regex.test(), regex.exec(), string.match(), string.matchAll(), string.search(), string.replace(), string.replaceAll(), string.split()
  • 最佳实践: 避免回溯失控,预编译正则,使用非捕获分组,并进行严格的错误处理。

通过本指南的学习,您应该已经对 JavaScript 正则表达式有了全面的理解,并能够将其应用于您的项目中,提高字符串处理的效率和代码的健壮性。


10. 附录:常用正则表达式速查表

元素描述示例
字符类
.匹配除换行符外任意单个字符 (带 s 标志时匹配所有)/a.b/ 匹配 a?b
\d数字 (0-9)/\d/ 匹配 1
\D非数字/\D/ 匹配 a
\w单词字符 (字母、数字、下划线)/\w/ 匹配 A, 1, _
\W非单词字符/\W/ 匹配 , -, !
\s空白符 (空格、制表符、换行符等)/\s/ 匹配
\S非空白符/\S/ 匹配 a
字符集
[abc]匹配 a, b, 或 c/[aeiou]/ 匹配任一元音
[a-z]匹配指定范围内的任一字符/[A-Z0-9]/ 匹配任一大写字母或数字
[^abc]匹配除了 a, b, 或 c 之外的任一字符/[^0-9]/ 匹配任一非数字字符
量词
*零次或多次/a* 匹配 "", a, aa
+一次或多次/a+/ 匹配 a, aa
?零次或一次 (可选)/a? 匹配 "", a
{n}恰好 n/\d{3}/ 匹配 123
{n,}至少 n/\d{3,}/ 匹配 123, 1234
{n,m}nm/\d{3,5}/ 匹配 123, 1234, 12345
*?, +?, ??非贪婪模式 (尽可能少地匹配)/a.+?b/ 匹配 axb 中的 axb
定位符
^字符串开头 (带 m 标志时为行开头)/^hello/ 匹配以 hello 开头的字符串
$字符串结尾 (带 m 标志时为行结尾)/world$/ 匹配以 world 结尾的字符串
\b单词边界/\bcat\b/ 匹配独立的 cat
\B非单词边界/\Bcat\B/ 匹配 tomcat 中的 cat
分组
(pattern)捕获分组/(ab)/ 捕获 ab
(?:pattern)非捕获分组/(?:ab)/ 分组 ab 但不捕获
(?<name>pattern)命名捕获分组/(?<year>\d{4})/
\n反向引用第 n 个捕获分组/(\w)\1/ 匹配 aa
其他
``或 (OR) 运算符
\转义特殊字符/\./ 匹配字面量的 .
预查 (高级)
(?=pattern)正向肯定预查 (后面跟着 pattern)/\w+(?=\s*美元)/ 匹配 50 (50美元)
(?!pattern)正向否定预查 (后面不跟着 pattern)/q(?!u)/ 匹配 Iraq 中的 q
(?<=pattern)反向肯定预查 (前面跟着 pattern)/(?<=¥)\d+/ 匹配 100 (¥100)
(?<!pattern)反向否定预查 (前面不跟着 pattern)/(?<!http:)\/\// 匹配非 http:////

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐