第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 修饰符。不过大多数业务场景不考虑,我至今只在古籍数字化项目里遇到过。

我曾经踩过的坑: 在 JavaScript 里没加 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 用来去除标签但会漏掉属性里的 >,慎用。
我的个人经验: 正则写好后,一定要用测试工具跑边界案例。空字符串、超长输入、特殊字符(如换行符)都是常见翻车点。我曾经写过一条手机号正则没考虑 \n,结果用户粘贴时带换行就挂了。

下面这张图能帮你直观理解正则引擎在匹配时的执行流程:输入文本 → 依次尝试每个分支 → 回溯 → 输出结果。看到回溯那步就明白为什么写得差的正则会卡死了。

正则表达式匹配流程 输入文本 正则引擎(尝试匹配) 匹配失败 回溯或报错 匹配成功 不匹配 / 异常 捕获结果 / 替换 量词和分支越多,回溯次数指数级增长 → 注意性能

最后提醒一句:正则不是万能的。遇到嵌套结构(比如括号的嵌套、HTML 嵌套标签),正则表达能力有限,该上解析器就得上解析器。规则配置里正则主要负责模式匹配和提取,复杂的逻辑校验留到代码层处理。