第4章 常用正则表达式技巧
正则表达式这东西,刚接触时觉得像天书,用多了就会发现它是规则配置里的瑞士军刀。我个人习惯把常用模式存成代码片段,写规则时直接拖过来改改。这一章我们专门聊几个高频场景:匹配中文、邮箱、手机号、URL,还有一些我踩过的坑。
4.1 匹配中文
先说中文。很多规则需要提取或验证汉字,比如屏蔽敏感词、校验姓名。中文字符的 Unicode 范围是 \u4e00-\u9fa5,加上全角标点就是 \u3000-\u303f,不过大部分场景用前者就够了。
/^[\u4e00-\u9fa5]+$/ 匹配纯中文,长度不限。如果是名字(两到四个字),可以加量词 {2,4}。
// 匹配纯中文字符串
const regex = /^[\u4e00-\u9fa5]{2,4}$/;
console.log(regex.test('张三')); // true
console.log(regex.test('爱新觉罗')); // true
console.log(regex.test('abc张三')); // false
嗯,这里要注意:有些生僻字超出基本平面,比如“𠀀”(U+20000),需要用 \x{20000}-\x{2FA1D} 配合 u 修饰符。不过大多数业务场景不考虑,我至今只在古籍数字化项目里遇到过。
u 修饰符,正则把 \u4e00 当成了四个字符,结果匹配失败。记得加 /u 处理 UTF-16 代理对。
4.2 匹配邮箱
邮箱正则大概是互联网上被复制最多次的代码之一。但很多网上找的要么太宽松(比如 /^[\w.-]+@[\w.-]+\.\w+$/ 会放过 a@b.c),要么太严格(把合法邮箱 user+tag@example.com 拒之门外)。
我个人习惯用 RFC 5322 简化版,兼顾准确和可读性:
// 可靠的邮箱正则(简化版)
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user.name+tag@example.co.uk')); // true
console.log(emailRegex.test('test@test')); // false(缺顶级域名)
为什么这么长?说白了,邮箱地址里的点号、加号、百分号都是合法的,域名最长 63 个字符,顶级域名至少 2 个字母。你可以在生产环境直接拿来用,我很多项目都用的这个版本。
| 场景 | 正则 | 说明 |
|---|---|---|
| 基础邮箱 | /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
快速前端校验,不严谨但够快 |
| 严格邮箱 | /^...(上面长正则)...$/ |
RFC 5322 简化版,生产推荐 |
| 仅匹配域名 | /(?<=@)[\w.-]+\.[a-z]{2,}$/i |
从邮箱里提取域名,用后行断言 |
4.3 匹配手机号
手机号匹配最烦人,因为不同国家格式差太多。这里只讲中国手机号(11位,1开头,第二位通常是3/5/7/8/9)。
// 中国大陆手机号(不含国际前缀)
const cnPhone = /^1[3-9]\d{9}$/;
console.log(cnPhone.test('13800138000')); // true
console.log(cnPhone.test('12345678901')); // false(第二位2不在范围内)
我在项目里还经常要匹配带国家码的,比如 +86 138-0013-8000。这时候得多一步预处理,或者用更灵活的模式:
// 允许 +86,允许空格、短横
const cnPhoneWithCode = /^(\+86[\s-]?)?1[3-9]\d[\s-]?\d{4}[\s-]?\d{4}$/;
console.log(cnPhoneWithCode.test('+86 138 0013 8000')); // true
console.log(cnPhoneWithCode.test('138-0013-8000')); // true
/^1\d{10}/ 这种,它会匹配 10000000000 这种不存在的号码。第二位范围务必限制。
4.4 匹配URL
URL 正则也是个老大难。协议、子域名、端口、路径、查询参数、锚点……写全了能绕晕。我平时直接用一个经过多年打磨的通用模式:
// 通用 URL 匹配(支持 http/https/ftp)
const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/[\w\-./?%&=]*)?$/i;
console.log(urlRegex.test('https://example.com/path?q=1#anchor')); // true
console.log(urlRegex.test('www.baidu.com')); // true(无协议也匹配)
如果只匹配带协议头的 URL,改成 /^https?:\/\/... 就行。你可能会问,要不要支持 IP 地址形式的 URL?嗯,http://192.168.1.1:8080 这种得单独处理,因为 IP 地址不是域名。
| 需求 | 正则 | 注意事项 |
|---|---|---|
| 提取URL | /(https?:\/\/[^\s]+)/g |
捕获组,注意避开标点结尾 |
| 验证URL | /^https?:\/\/[\w.-]+(:[0-9]+)?(.*)$/i |
端口可选,路径用 .* 宽容 |
| 仅域名 | /^(?:https?:\/\/)?([\w-]+\.[\w.-]+)/i |
第一捕获组取出域名 |
4.5 其他高频技巧
除了上面四种,还有一些场景在规则配置里很常见。我简单列一下:
- IP 地址:
/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/—— 每个段0-255,别用\d{1,3}那种会匹配 999 的。 - 纯数字:
/^\d+$/,如果要限制长度就/^\d{6,8}$/。 - 日期格式 YYYY-MM-DD:
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/—— 注意月份不能是 13,日期不能是 32。 - HTML 标签(简易):
/<[^>]+>/g用来去除标签但会漏掉属性里的 >,慎用。
下面这张图能帮你直观理解正则引擎在匹配时的执行流程:输入文本 → 依次尝试每个分支 → 回溯 → 输出结果。看到回溯那步就明白为什么写得差的正则会卡死了。
最后提醒一句:正则不是万能的。遇到嵌套结构(比如括号的嵌套、HTML 嵌套标签),正则表达能力有限,该上解析器就得上解析器。规则配置里正则主要负责模式匹配和提取,复杂的逻辑校验留到代码层处理。