JavaScript 正则表达式(Regular Expressions)完整参考指南:模式匹配的艺术
好的,作为{{IDENTITY}},我将为您呈现一份关于 JavaScript 中**正则表达式(Regular Expressions,简称 Regex 或 RegExp)**的完整参考指南。这份文档将涵盖正则表达式的基础概念、创建方法、核心语法、常用操作方法,以及在实际开发中的应用场景和最佳实践,旨在提供全面、高质量、富有教育价值的内容。
在 JavaScript 开发中,处理字符串是日常任务。无论是验证用户输入(如邮箱、电话号码)、从文本中提取特定信息(如 URL、日期),还是进行复杂的字符串查找和替换,常规的字符串方法往往力不从心。这时,**正则表达式(Regular Expressions,简称 Regex 或 RegExp)**便成为了我们手中的强大工具。
正则表达式是一种强大的模式匹配语言,它允许您用简洁而富有表现力的语法来定义字符串的搜索模式。掌握正则表达式,将极大地提升您处理文本数据的能力,使您的代码更加高效、灵活和健壮。
本指南将带您全面深入地了解 JavaScript 中的正则表达式,从最基本的概念到高级特性,助您解锁字符串操作的无限可能。
前置知识
为了更好地理解本指南内容,建议您具备以下 JavaScript 基础知识:
- JavaScript 基础: 熟悉字符串、函数、数组、对象等基本数据类型和结构。
- 基本字符串操作: 对
String.prototype上的一些基本方法(如indexOf,slice等)有所了解。
目录
- 什么是正则表达式?
- 1.1 定义与核心用途
- 1.2 为什么需要正则表达式?
- 创建正则表达式
- 2.1 字面量语法 (Literal Syntax)
- 2.2
RegExp构造函数 (Constructor) - 2.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)
- 3.1
- 正则表达式模式的核心元素
- 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)
- 正向肯定预查
- 4.2.1 字符类 (Character Classes)
RegExp对象的方法- 5.1
regex.test(string) - 5.2
regex.exec(string)
- 5.1
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)
- 6.1
- 实际应用示例
- 7.1 邮箱地址验证
- 7.2 URL 提取
- 7.3 日期格式转换
- 7.4 密码强度检查
- 7.5 HTML 标签替换 (简单示例)
- 性能考量与最佳实践
- 8.1 避免回溯失控 (Catastrophic Backtracking)
- 8.2 预编译正则表达式
- 8.3 使用非捕获分组
(?:) - 8.4 谨慎使用
.和* - 8.5 正则表达式调试工具
- 总结
- 附录:常用正则表达式速查表
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”。
- 匹配任何数字字符 (0-9)。等同于
-
\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} | n 到 m 次 | /\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:// 的 // |
更多推荐


所有评论(0)