表单校验
手机号、邮编、日期格式、密码强度——课程第 22–24 关的三个模式可以直接用到前端表单里;记住加锚点,并把「2 月 30 日」这类语义检查留给代码。
使用指南
正则表达式课是一门 25 关的交互式教程:每一关给你一段测试文本和一个目标(「匹配这几个、不要匹配那几个」或「捕获出某一部分」),你在输入框里改表达式,右侧实时高亮所有匹配和分组,判定器逐项告诉你哪些目标已经达成。全部判定用浏览器自带的 JavaScript 正则引擎完成,不上传任何文本。
更新于 2026-09-094 个来源约 13 分钟读完
正则表达式课是一门 25 关的交互式教程:每一关给你一段测试文本和一个目标(「匹配这几个、不要匹配那几个」或「捕获出某一部分」),你在输入框里改表达式,右侧实时高亮所有匹配和分组,判定器逐项告诉你哪些目标已经达成。全部判定用浏览器自带的 JavaScript 正则引擎完成,不上传任何文本。
课程按难度递进:先是字面量、点号、转义这些「找字符」的基础;然后是字符类与范围、预定义类 \d \w \s、四种量词;接着是锚点与单词边界、选择符、三种分组与反向引用、贪婪与懒惰;再进入先行/后行断言与标志;最后用三个真实模式(中国大陆手机号、邮箱、日期)综合练习,并以一关「灾难性回溯」收尾,让你亲手写出一条会让引擎卡住的表达式,再修好它。
学完大约需要 60 分钟。适合从来没写过正则、或者「会抄不会改」的人;已经熟悉正则的读者可以直接跳到第 17 关之后的环视与陷阱部分。
?lesson=n 的链接,打开后直接落在那一关。「重置进度」清空本课程全部记录。本页上方就是课程本体:左侧目录选关,中间输入正则,判定实时出现。
第 14 关「捕获组」的目标是从 2026-09-09 里取出年份。下面是一次通关过程的记录:
测试文本 发布日期:2026-09-09,截止:2027-01-31
第一次尝试 \d+
匹配 2026 09 09 2027 01 31 ← 6 处匹配,没有捕获组
判定 ✕ 只应匹配两个完整日期 ✕ 组 1 应为年份
第二次尝试 (\d{4})-\d{2}-\d{2}
匹配 2026-09-09 2027-01-31 ← 2 处匹配
组 1 2026 2027
判定 ✓ 匹配数 2 ✓ 组 1 = 2026 / 2027 → 通关
第 25 关「灾难性回溯」则反过来:先让你写出 ^(a+)+$ 这类嵌套量词模式,对一串 aaaaaaaaaaaaaaaaaaaaaaaaaaaa! 测试,页面会报告尝试次数呈指数增长;再要求改成 ^a+$ 这样的等价写法通关。
正则表达式(regular expression)用一小套符号描述「一类字符串长什么样」。字母数字大多代表自己,十几个元字符(. * + ? ^ $ | ( ) [ ] { } \)则有特殊含义。引擎拿着模式从左到右扫描文本,在每个位置尝试匹配,找到就报告位置与长度。这门课用的是 JavaScript(ECMAScript)方言,它与 Python、Java、Go 的正则在基础语法上几乎一致,差别集中在环视、Unicode 属性与部分标志上。
cat 就匹配连续的 c、a、t 三个字符。点号 . 匹配除换行外的任意一个字符,所以 c.t 能匹配 cat、cut、c9t。当你真的想找一个句点(比如文件扩展名前的点)时,要写 \.——反斜杠让元字符回到字面意义。所有元字符都可以这样转义;在字符类里只有 ] \ ^ - 需要转义。
方括号列出候选字符:[aeiou] 匹配任一元音;[a-z]、[0-9] 用连字符表示范围,可以并列写成 [A-Za-z0-9_];开头加 ^ 取反,[^0-9] 匹配任何非数字。三个常用的预定义类是它们的缩写:
| 类 | 等价于 | 说明 |
|---|---|---|
\d |
[0-9] |
数字(不含中文数字与全角数字) |
\w |
[A-Za-z0-9_] |
「单词字符」,注意汉字不算 |
\s |
[ \t\n\r\f\v…] |
空白,含全角空格 |
大写版本 \D \W \S 是各自的取反。\w 不含汉字是中文用户最常踩的坑:匹配中文要写 [\u4e00-\u9fa5],或在 u 标志下用 \p{Script=Han}。
量词跟在一个原子(单个字符、字符类或分组)后面,规定它重复几次:
* 零次或多次,+ 一次或多次,? 零次或一次;{n} 恰好 n 次,{n,} 至少 n 次,{n,m} n 到 m 次。\d{4} 比 \d\d\d\d 更好读,\d{11} 是手机号位数。量词只作用于紧挨着它的那一个原子:ab+ 是 a 后面跟一个或多个 b,要重复整个 ab 得写 (ab)+。
^ 和 $ 不匹配字符,只匹配位置:字符串开头和结尾(多行模式下是每一行的开头结尾)。表单校验几乎总要写 ^…$,否则 \d{11} 会「匹配」到 12 位号码的前 11 位。\b 是单词边界——\w 与 \W(或串首尾)之间的位置。\bcat\b 能匹配 a cat 而不匹配 concatenate。同样因为 \w 不含汉字,\b 在中文里基本无用。
| 表示「或」,优先级最低:cat|dog 匹配两个词之一;要限定范围要用括号,gr(a|e)y。括号同时创建捕获组:匹配成功后可以按编号取出组内文本(match[1]),或在替换字符串里用 $1 引用——这是正则最有生产力的用法之一,比如把 2026-09-09 换成 09/09/2026 只需一次 replace。
(?<year>\d{4}) 是命名组,用 groups.year 读取,比数字编号更不容易在改模式时错位。(?:…) 是非捕获组:只分组、不占编号,适合只是想给量词或选择划范围的场合。\1 是反向引用——要求这里再出现一次第 1 组捕获到的内容:(\w)\1 匹配 ll、oo 这样的双写字母,<(\w+)>.*?</\1> 匹配成对的 HTML 标签。
量词默认是贪婪的:尽量多吃,吃多了再吐。对 "a" and "b" 用 ".+" 会一口气匹配从第一个引号到最后一个引号的全部内容。在量词后加 ? 变成懒惰:".+?" 尽量少吃,得到 "a" 和 "b" 两个匹配。另一种更精确的写法是用否定字符类 "[^"]+",它不依赖回溯,通常也更快。
环视(lookaround)像锚点一样只匹配位置,但条件是「旁边有没有某个模式」:
(?=…) 先行:后面必须跟着 …;\d+(?=元) 匹配 100元 里的 100,但不把「元」算进匹配。(?!…) 否定先行:后面不能是 …。(?<=…) 后行:前面必须是 …;(?<=¥)\d+ 匹配价格数字而不含货币符号。(?<!…) 否定后行。JavaScript 自 ES2018 起支持后行断言,Safari 16.4 之后才全面可用;Python 的后行断言要求定长。密码强度校验是环视的典型用法:^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$ 用三个先行断言从同一位置分别检查「含数字、含小写、含大写」,然后 .{8,} 消耗整个串。
标志写在模式外面:i 忽略大小写;g 找出所有匹配而不是第一个(课程里的高亮始终用 g);m 让 ^ $ 匹配每一行;s 让 . 也能匹配换行;u 开启 Unicode 模式,. 按码位而不是 UTF-16 码元匹配,emoji 才算一个字符;y 粘连匹配,从 lastIndex 处必须立刻匹配。
中国大陆手机号:^1[3-9]\d{9}$——1 开头、第二位 3–9、共 11 位。不要试图枚举运营商号段,号段每年都在增加。
邮箱:完全符合 RFC 5322 的正则长达数千字符且并不实用。表单里的合理写法是 ^[^\s@]+@[^\s@]+\.[^\s@]+$:有且只有一个 @、两侧非空、域名部分至少一个点。真正的验证靠发一封确认邮件。
日期:^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ 能限定月份 01–12、日 01–31,但不能知道 2 月有几天——语法校验交给正则,语义校验交给代码。
JavaScript、Python、Java、PCRE 的正则引擎都是回溯式(backtracking):碰到多种可能的分法就先试一种,失败再退回来试下一种。嵌套量词让分法数量呈指数增长:^(a+)+$ 面对 30 个 a 加一个 !,引擎要试超过 2^30 种切分方式才能宣告失败,浏览器标签页会卡死几秒到几分钟。这类模式若接受用户输入,就是一种拒绝服务攻击(ReDoS)。
避免的原则:不让两个相邻的量词能匹配同样的字符((a+)+、(\w+\s?)+、(.*)* 都是危险信号);用否定字符类取代懒惰的 .*?;给输入设长度上限。Go 与 Rust 的正则库采用 Thompson NFA / DFA 实现,保证线性时间,但代价是不支持反向引用与环视——Russ Cox 的文章把两种实现的差异讲得最清楚。
匹配嵌套结构(括号配对、HTML 全文)、需要「计数」的任务(判断左右括号数相等)、依赖上下文语义的判断(这个数字是不是合法日期),正则都做不到或做得很难。它是词法层的工具:找出 token、切分、简单校验、批量替换——在这些事上它无可替代。
(?i) 内联修饰符、Python 的 (?P<name>)、Java 的 \p{javaLowerCase} 等方言差异,也不讲 POSIX 字符类 [[:alpha:]]。u;\p{…} 属性转义、v 标志的集合运算不在课程范围。\w 能匹配汉字」:不能。\w 只有 ASCII 字母、数字和下划线,匹配中文用 [\u4e00-\u9fa5] 或 \p{Script=Han}(需 u 标志)。. 匹配任何字符」:默认不匹配换行;要跨行匹配加 s 标志或用 [\s\S]。\d{11} 就只接受 11 位」:没有锚点时它会在更长的数字串里找到一段 11 位;校验一定要 ^…$。[A-z] 是所有字母」:ASCII 里大写 Z(90)和小写 a(97)之间还有 [ \ ] ^ _ \`` 六个符号,会被一并匹配;要写 [A-Za-z]`。ab+ 只重复 b;ab{2} 匹配 abb,不匹配 abab。>、注释都会让正则失效,请用 DOM 解析器。手机号、邮编、日期格式、密码强度——课程第 22–24 关的三个模式可以直接用到前端表单里;记住加锚点,并把「2 月 30 日」这类语义检查留给代码。
VS Code、Sublime、JetBrains 系列的查找替换都支持正则与 $1 引用。学完分组一节,你可以一次把 2026-09-09 全部改成 09/09/2026,或给所有 console.log( 加上前缀。
从几万行日志里抓出 IP、状态码、耗时:(\d+\.\d+\.\d+\.\d+).*?" (\d{3}) (\d+)ms 之类的模式配合命名组,几行脚本就能出统计。
看到 (\w+\s?)+$、(.*a)+、(a|aa)+ 这类嵌套或重叠量词出现在处理用户输入的位置,就该要求改写或加长度限制。
判定看的是结果不是写法:只要应匹配的全部命中、不应匹配的全部落空、捕获组内容一致,就算通关。参考答案只是其中一种写法。
通常是「不应匹配」的片段也被匹配了,或者匹配范围比目标多了一个字符(比如把标点吃进去了)。看结果条里哪一项是 ✕。
第 1–18 关的语法在主流语言里完全通用。环视一关的后行断言在 Python 里要求定长,命名组写法 Python 是 (?P<name>…);标志的写法各语言不同。
存在当前浏览器的 localStorage 里,不上传。换浏览器或清除站点数据会丢;可以用「分享本关」的链接记下学到哪一关。
演示用的是模拟步数计数,超过阈值就判为「灾难性回溯」。真的执行会让整个标签页无响应,无法给你反馈。
课程完全在浏览器本地运行:你输入的表达式与测试文本只交给浏览器自带的正则引擎,不发送到任何服务器;通关进度保存在本机 localStorage。关闭页面后输入内容即被释放。
更新于 2026-09-09
25 关交互式正则教程:字面量、字符类、量词、分组、环视到回溯陷阱,改表达式即时看匹配
目标写一个表达式,匹配所有含有 cat 的词,不匹配其它词。
最简单的正则就是一串普通字符:cat 会在文本里寻找连续的 c、a、t。正则默认是「包含」而不是「等于」——concat 和 category 里都有 cat,所以也算匹配。字母、数字、空格、中文都是字面量,只有十几个符号有特殊含义,后面几关逐个介绍。