正则表达式入门:从零到精通

正则表达式(Regular Expression,简称 regex、regexp 或 RE)是一种用来描述字符串模式的微型语言。它的核心作用只有一件事:在文本中"找模式"——验证格式、提取子串、批量替换、拆分字段。无论你写 JavaScript、Python、Go 还是 Java,都逃不开它。本篇 regex 教程 帮你从零基础一次打通,写出高效、可读的正则。

1. 字符类:定义"匹配什么字符"

字符类决定"一个位置能放什么字符",是最基础也是最高频的语法单元:

[abc]    // 匹配 a、b 或 c 中任意一个
[a-z]    // 匹配 a 到 z 之间任意小写字母
[A-Z0-9] // 匹配大写字母或数字
[^0-9]   // 字符类内 ^ 表示"取反":匹配任何数字字符
\d       // 等价于 [0-9],匹配数字
\w       // 等价于 [A-Za-z0-9_],匹配单词字符
\s       // 匹配任意空白:空格、Tab、换行
.        // 匹配除换行外的任意字符(dotAll 标志 s 下可匹配换行)

反义(取大写)规则简单易记:\D = 非数字,\W = 非单词字符,\S = 非空白。比如要"匹配一段不含空格的字符串",用 \S+ 即可。

2. 量词:定义"重复几次"

量词紧跟在一个原子(字符、字符类或分组)后面,控制它的重复次数:

*      // 0 次或多次(等价于 {0,})
+      // 1 次或多次(等价于 {1,})
?      // 0 次或 1 次(等价于 {0,1}),也用于非贪婪
{n}    // 恰好 n 次
{n,}   // 至少 n 次
{n,m}  // n 到 m 次之间

贪婪 vs 非贪婪:默认量词是贪婪的——能多匹配就多匹配。在量词后加 ? 变成非贪婪(懒惰)——能少匹配就少匹配。看下面的对比:

输入:<b>hello</b><b>world</b>

/<.*>/   // 贪婪:匹配整段 <b>hello</b><b>world</b>
/<.*?>/  // 非贪婪:只匹配 <b>

3. 锚点与边界

锚点是"零宽"位置——它们不消耗字符,只标记位置:

^   // 行首(多行模式 m 下匹配每行开头)
$   // 行尾(多行模式 m 下匹配每行结尾)
\b  // 单词边界:\w 与 \W 之间的位置
\B  // 非单词边界

验证手机号时 ^1[3-9]\d{9}$ 用到了 ^$,确保整段都符合模式,而不是只在中间某处匹配到。

4. 分组与引用

括号让多个字符"打包"成一个整体,并提供捕获能力:

(abc)       // 捕获组:匹配 abc,并把结果存入 $1 或 groups[1]
(?:abc)      // 非捕获组:仅分组,不占用捕获槽位,性能略好
(a|b)        // 匹配 a 或 b(分支)
(?<name>x)  // 命名捕获组(ES2018+),引用时用 \k<name>

反向引用 \1 表示"匹配第 1 个捕获组曾经匹配到的同样内容",是匹配成对 HTML 标签的关键。

5. 四个实战模式

① 邮箱验证

日常场景用简化版即可。完整的 RFC 5322 邮箱正则有 6000+ 字符,几乎没有意义:

^[\w.+-]+@[\w-]+\.[\w.-]+$

说明:本地部分允许字母、数字、_ . + -;域名部分用 \.[\w.-]+ 兼容多级子域,如 user@mail.dev.example.co

② URL 提取

https?://[\w.-]+(?:/[\w./?=&%-]*)?

https? 同时匹配 http 和 https;路径部分用 (?:/...)? 表示可选的查询字符串。

③ 中国大陆手机号

^1[3-9]\d{9}$

11 位数字,首位 1,第二位 3-9(涵盖 13x/14x/15x/16x/17x/18x/19x 号段),后 9 位任意数字。

④ HTML 标签匹配

<([a-z]+)([^>]*)>(.*?)<\/\1>

([a-z]+) 捕获标签名,([^>]*) 捕获属性串,(.*?) 非贪婪匹配内容,<\/\1> 通过反向引用保证开始与结束标签名一致。生产环境请勿用正则解析 HTML——请使用专门的解析器(如 DOMParser、BeautifulSoup)。

6. 零宽断言(Lookaround)

零宽断言匹配的是"位置"而不是"字符",常用于"在某个上下文里取值":

(?=foo)    // 先行断言:右侧是 foo
(?!foo)    // 负先行断言:右侧不是 foo
(?<=foo)   // 后行断言:左侧是 foo
(?<!foo)   // 负后行断言:左侧不是 foo

例:从 ¥199 中只取数字:(?<=¥)\d+

7. JavaScript 中的正则

JS 中正则有两种写法,效果等价:

// ① 字面量(推荐,编译期就确定,性能略好)
const re = /ab+c/i;

// ② 构造函数(适合模式由变量拼接的情况)
const pattern = "ab+c";
const re2 = new RegExp(pattern, "i");

常用 flag:

g  // global:匹配所有出现,不加则只匹配第一个
i  // case-insensitive:忽略大小写
m  // multiline:^ 和 $ 匹配每行的开头/结尾
s  // dotAll:让 . 也能匹配换行(ES2018)
u  // unicode:启用 Unicode 模式
y  // sticky:从 lastIndex 处开始匹配

三个最常用的方法:

// test:返回 boolean
/^1[3-9]\d{9}$/.test("13800138000");  // true

// match:返回匹配结果数组(无 g 标志时含捕获组)
"foo bar".match(/(\w+)\s(\w+)/);
// ["foo bar", "foo", "bar"]

// replace:第二个参数可以是字符串或函数
"hello world".replace(/\w+/g, m => m.toUpperCase());
// "HELLO WORLD"

注意全局标志 g 的副作用:当正则设置了 gy,会启用 lastIndex 状态。如果把它赋给变量反复调用 test()exec(),会从上次结束位置继续——这是新人最常踩的坑。解决办法是用字面量创建新对象,或在循环里手动 re.lastIndex = 0

8. 常见陷阱

① 贪婪回溯导致性能塌方:写 (.*) 套多层很容易在长文本上 O(n²) 甚至指数级。优先用具体字符类([^>]*[^"]*),并尽量加 ? 改为非贪婪。

[.]\. 的区别:字符类里的点不需要转义,[.] 就是匹配一个普通点;而 \. 是为了避免被当成"任意字符"。

③ 字符类里的元字符]^-\[] 内有特殊含义,需要时放在开头或转义。

④ PCRE vs JavaScript 正则的差异:PHP、Python(re 模块底层)等很多语言使用 PCRE 引擎,支持环视占有量词 *+递归、条件等高级特性。JavaScript 的正则引擎(ECMAScript)原生不支持这些,复杂场景需要额外库(如 XRegExp)。日常 95% 的需求两者都能胜任。

9. 用 DevToolbox 在线测试

步骤 1:打开 https://devstoolbox.net/tools/regex-tester.html

步骤 2:在 Pattern 输入框写正则,下方支持选择 g / i / m / s 等 flag

步骤 3:把待测文本贴到 Test 区域,匹配项会高亮显示

步骤 4:右侧实时显示捕获组、字符位置、十六进制码点

步骤 5:语法错误会给出具体位置和原因提示(例如"Unmatched closing parenthesis")

这是真正好用的 regex tester online:实时高亮、捕获组可视化、纯前端运行、文本不上传服务器。

10. 常见问题 FAQ

正则表达式难学吗?

核心语法只有 20 多个符号,30 分钟就能上手。难的是"读别人写的复杂正则"和"在性能与可读性之间权衡"。建议先掌握字符类、量词、分组、断言这四大件,其余按需查表。

如何在 JS 中用变量构造正则?

new RegExp() 构造函数。要注意:字符串里的 \d 实际是 \\d(反斜杠要转义):

const word = "hello";
const re = new RegExp(`\\b${word}\\b`, "gi");
"Hello hello HELLO".match(re);  // ["Hello", "hello", "HELLO"]

如果 word 来自用户输入,务必先做 escape(),防止注入元字符。

String.match() 和 RegExp.exec() 的区别?

g 标志时两者行为基本一致,都返回第一个匹配的完整信息和捕获组。带 gexec() 仍返回第一条但会更新 lastIndex,方便循环遍历所有匹配;match() 则直接返回所有匹配结果数组,不再返回捕获组细节。需要捕获组时优先 exec()matchAll()(ES2020)。

为什么 (.*) 匹配很慢?

.* 在贪婪模式下会先吃光所有字符,再逐步回溯寻找后续模式能匹配的位置。如果后续匹配失败,会触发大量回溯,复杂情况下可能灾难性回溯(catastrophic backtracking)使 CPU 飙到 100%。优化思路:① 缩小 .* 范围(如 [^<]*),② 用 *? 非贪婪,③ 把确定性部分写在前,缩小回溯空间。

PCRE 和 JavaScript 正则的差异?

PCRE(PHP、Python re、Perl 等)功能更全:支持占有量词 *+、递归 (?R)、条件匹配、内联修饰符 (?i)... 等。JS 的 ECMAScript 正则实现较精简,环视和命名捕获组直到 ES2018 才补齐,递归、占有量词至今没有。写跨平台正则时避免使用 JS 不支持的语法。

结论

正则表达式是"一次学习、终身受益"的工具:语法极简、跨语言通用、应用场景无处不在。掌握字符类、量词、分组、断言这四件套,配合 regex tester online 工具反复练习,你很快就能写出干净、高效的正则。DevToolbox 的在线正则测试器免注册、纯前端运行,文本不离开你的浏览器,是日常调试的好帮手。

立即打开正则测试器 → https://devstoolbox.net/tools/regex-tester.html

相关文章

如何格式化 JSON:完整指南

UUID 生成器完全指南