简短回答: 下面五条是常用的写法,都只是"格式检查",不是"真实性检查"。手机号 ^1[3-9]\d{9}$ 只能说明长这样,不能说明号码存在;邮箱和网址的正则会放过一些无效的、拒绝一些有效的;日期正则不知道 2 月只有 28 或 29 天;中文字符 [一-龥] 会漏掉生僻字,推荐用 \p{Script=Han} 加 u 标志位。每条我都在 Node 25.8.0 里用至少 8 个"应通过"和 8 个"应拒绝"的样例跑过,并用 Python 3.9.6 的 re 交叉验证。
想边看边试,可以用免费的正则表达式测试工具。它运行的是 JavaScript 正则,匹配在后台线程里完成,超时会被停止;文本留在浏览器里,不上传。
1. 手机号(中国大陆)
/^1[3-9]\d{9}$/
实测(Node):13800138000、15912345678、18612345678、19912345678、13012345678、14512345678、16612345678、17712345678、17000000000 共 9 个全部通过;12800138000(第二位是 2)、1380013800(10 位)、138001380001(12 位)、+8613800138000、138 0013 8000、1380013800a、全角数字 13800138000、10086 等 10 个全部拒绝。
局限:它只检查"1 开头、第二位 3 到 9、一共 11 位"。号段会随运营商和监管调整,这个范围是我写的常见近似,不是官方号段表,我没有核对过任何官方清单;17000000000 能通过,不代表这是个能用的号码。要确认号码真的属于用户,只能发短信验证码。带 +86 或空格的输入,需要先去掉再校验。
2. 邮箱
/^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/
实测:a@b.com、ana.lopez@example.com、sales+uk@shop.example.co.uk、user_name@mail.example.org、x-y@sub.domain.io、123@qq.com、first.last@gmail.com 通过;name@、@host.com、a@b、a b@example.com、a@b.c、a@@b.com、user@exa mple.com、用户@example.com、a@example.com. 等 10 个拒绝。
局限很明显:a@-b.com、a..b@example.com、.a@example.com 这三个无效或可疑的地址同样通过了,实测返回 true。反过来,它拒绝中文用户名之类的国际化邮箱。真实的邮箱规则(RFC 5321/5322)复杂得多,正则无法完整校验。实际做法是:用宽松的正则挡掉明显的输入错误,再发一封确认邮件。
3. 网址
/^https?:\/\/[^\s\/$.?#][^\s]*$/
实测通过:https://obelisk.club、http://localhost:3000/api、https://example.com/docs?page=2#top、http://192.168.0.1/a、https://a.b/c%20d、https://例子.中国/路径 等 9 个;拒绝:example.org、ftp://example.com、https://、https:// example.com、http:///path、//example.com、https://a b.com、https://.com、www.example.com 等 10 个。
局限:http://x、http://a..b 也通过了,它不检查域名是否合法或存在;不带 http(s):// 的 www.example.com 会被拒绝。需要严格解析时,用语言自带的 URL 解析器(如 JavaScript 的 new URL()),不要靠正则。
4. 日期(YYYY-MM-DD)
/^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/
实测通过 9 个:2026-10-20、2000-01-01、1999-12-31、2024-02-29、9999-12-31、0000-01-01,以及三个现实中不存在的 2023-02-29、2023-02-30、2023-04-31。拒绝 11 个:2026-13-01、2026-00-10、2026-10-32、20261020、2026/10/20、2026-1-5、10-20-2026、带尾部空格的 2026-10-20 等。
局限:它只管月份 01 到 12、日 01 到 31,不懂大小月和闰年,所以 2 月 30 日照样通过。要判断日期是否真实存在,先用正则确认格式,再交给日期库去解析。
5. 中文字符
常见写法是 [一-龥],即 U+4E00 到 U+9FA5。实测它漏掉很多汉字:
| 字符 | 码点 | [一-龥] | \p{Script=Han}(u) |
|---|---|---|---|
| 你 | U+4F60 | 匹配 | 匹配 |
| 龥 | U+9FA5 | 匹配 | 匹配 |
| 龦 | U+9FA6 | 不匹配 | 匹配 |
| 㐀(扩展 A) | U+3400 | 不匹配 | 匹配 |
| 𠀀(扩展 B) | U+20000 | 不匹配 | 匹配 |
豈(兼容汉字,注意是 U+F900 而非普通的“豈”U+8C48) | U+F900 | 不匹配 | 匹配 |
| 〇 | U+3007 | 不匹配 | 匹配 |
| ,。!(全角标点) | U+FF0C 等 | 不匹配 | 不匹配 |
所以更稳的写法是 /\p{Script=Han}/u。注意:必须带 u 标志位,而且 \p{Script=Han} 包含 〇、々 这类不算"表意文字"的字符;全角标点不属于 Han,要匹配标点需另写,例如 [,。!?、]。Python 的 re 不支持 \p{...},我实测 re.compile(r'\p{Script=Han}') 直接报 bad escape \p,需要用第三方库 regex(我没有安装测试)或自己写码点范围。[一-鿿] 在 Node 和 Python 中结果一致,也会漏掉扩展区。
标志位:g i m s u
在 Node 里的实测结果:
g(全部匹配):"Cat cat\nCAT"用/cat/只得到第一个匹配cat;/cat/gi得到["Cat","cat","CAT"]。i是忽略大小写。m(多行):对"ab\ncd",/^\w/g只得到["a"],加m后是["a","c"]。s(点号匹配换行):/a.b/.test("a\nb")是false,加s后是true。u(Unicode):"😀".match(/./g)得到两个半截代理字符,加u后得到完整的😀("😀".length是 2)。\p{...}也需要它。
贪婪与懒惰
. 默认贪婪,会尽量多吃。对 <b>one</b><b>two</b>:/<b>.<\/b>/ 匹配整个字符串;/<b>.?<\/b>/g 在 后加 ? 变成懒惰,得到 ["<b>one</b>","<b>two</b>"]。Python 的结果相同。用正则解析 HTML 仍然不可靠,这里只是演示语法。
灾难性回溯
嵌套重复加上一个会失败的结尾,会让引擎尝试指数级的路径。/^(a+)+$/ 去匹配 n 个 a 加一个 !:
- Node:n=26 约 307 毫秒,n=28 约 1242 毫秒(时间每次略有波动,我另一轮跑 n=24 还出现过 558 毫秒,所以只看趋势:每多两个字符大约变成 4 倍)。
- Python 3.9.6:n=24 约 356 毫秒,同样呈指数增长。
- 对照
/^a+$/:26 个 a 加!只要 0.018 毫秒,10000 个也只要 0.039 毫秒。
我没有在工具页面里实际触发超时。按页面源码,后台线程超过所选时限(1、3 或 10 秒,默认 1 秒)会被终止,并显示提示。避免的办法:去掉嵌套重复,让多选分支互不重叠,或限制输入长度。
JavaScript 和 Python 的差异
以下都是我实际对照得到的:
$与结尾换行: Python 的^1[3-9]\d{9}$对"13800138000\n"返回匹配,JavaScript 不匹配。Python 里用\Z或re.fullmatch。\d的范围: Python 的\d默认匹配全角数字,re.search(r'\d','5')为True,加re.A后为False;JavaScript 的\d永远只匹配 ASCII。这让日期正则在 Python 中通过了2026-10-20。\w同理:Python 匹配"中",JavaScript 不匹配。- 命名分组: JavaScript 写
(?<y>...),Python 写(?P<y>...),用(?<y>会报unknown extension ?<y。 - 后行断言:
(?<=USD|EU)在 JavaScript 可用,Python 报look-behind requires fixed-width pattern。 \p{...}: 见上,Python 标准库re不支持。- 内联标志: Python 可写
(?i)abc,JavaScript 会报Invalid group;Node 25 支持(?i:a)b这种局部写法,其他引擎和旧版本我没有测。
常见问题
手机号正则 ^1[3-9]\d{9}$ 可以用来验证手机号吗?
只能做格式检查。它确认是 11 位、1 开头、第二位 3 到 9,不能确认号码存在或属于本人,号段也会变化。真正验证要靠短信验证码。
邮箱能用正则完整校验吗?
不能。邮箱规范很复杂,我实测的常用写法会放过 a..b@example.com 这样的可疑地址,也拒绝国际化邮箱。常见做法是用宽松的正则挡明显的错误,再发确认邮件。
怎么用正则匹配中文字符?
用 /\p{Script=Han}/u。常见的 [一-龥] 只覆盖 U+4E00 到 U+9FA5,会漏掉 龦(U+9FA6)、㐀、𠀀 等字。标点不属于 Han,要另外列出。
为什么同一个正则在 Python 和 JavaScript 结果不同?
两者对 $、\d、命名分组、后行断言的规则不同。例如 Python 的 $ 能匹配结尾换行前,\d 默认包含全角数字。换语言之前要在目标语言里重新测一遍,可以先在正则测试工具里验证 JavaScript 的行为;处理网址编码可以用 URL 编码工具,格式化 JSON 用 JSON 格式化工具。