正则的定位
正则表达式是一种「描述文本模式」的小语言。它的优势是短,劣势是难读、难调试、性能不可控。
所以我的用法是:简单匹配用字符串方法,复杂模式才上正则。下面这几种情况完全不需要正则:
js
str.includes("error") // 判断包含
str.startsWith("http://") // 判断前缀
str.split(",") // 按固定字符切分
元字符速查
真正高频的元字符其实只有十来个:
| 写法 | 含义 | 记忆点 |
|---|---|---|
. | 任意一个字符(不含换行) | 最贪心的那个 |
\d | 数字 | digit |
\w | 字母数字下划线 | word |
\s | 空白(空格、制表、换行) | space |
^ $ | 行首、行尾 | 锚点 |
[] | 字符集合,取其中一个 | [abc] 表示 a 或 b 或 c |
[^] | 取反集合 | [^0-9] 表示非数字 |
* + ? | 0 次以上、1 次以上、0 或 1 次 | 量词 |
() | 分组与捕获 | 分组内可整体加量词 |
| ` | ` | 或 |
大写形式是取反:\D 非数字、\W 非单词字符、\S 非空白。
量词与贪婪
量词默认是贪婪的,会尽量多匹配:
js
const html = "<b>粗体</b>和<i>斜体</i>";
// 贪婪:一路吃到最后一个 >
html.match(/<.+>/)[0]; // "<b>粗体</b>和<i>斜体</i>"
// 非贪婪:匹配到第一个 > 就停
html.match(/<.+?>/)[0]; // "<b>"
在量词后面加 ? 就变成非贪婪。解析结构化文本时,非贪婪几乎总是你想要的。
分组:捕获与不捕获
括号有两个作用:把多个字符当成一个整体、把匹配到的内容提取出来。
js
const date = "2026-09-23";
const m = date.match(/(\d{4})-(\d{2})-(\d{2})/);
m[0]; // "2026-09-23" 整个匹配
m[1]; // "2026" 第一个捕获组
m[2]; // "09"
如果只是想把内容分组、并不需要提取,用 (?:...) 写成不捕获组,能少占一个编号,也稍微快一点:
js
/(?:https?|ftp):\/\//.test(url);
常见场景的写法
js
// 手机号(宽松校验,只判断 1 开头的 11 位数字)
/^1\d{10}$/
// 邮箱(不追求完全符合 RFC,够用即可)
/^[\w.+-]+@[\w-]+\.[\w.]{2,}$/
// URL
/^https?:\/\/[^\s]+$/
// 日期 YYYY-MM-DD
/^\d{4}-\d{2}-\d{2}$/
// 中国大陆身份证(18 位,最后一位可能是 X)
/^\d{17}[\dXx]$/
邮箱和手机号不要试图用正则做严格校验。RFC 允许的邮箱格式极其复杂,写一个完全正确的正则既没必要也维护不了。正确做法是:正则做基础格式过滤,真伪交给发送验证码验证。
边界匹配
忘记加锚点是新手最常见的 bug:
js
// 危险:只要字符串里任何位置有 11 位数字就通过
/1\d{10}/.test("我的号码是13800138000,请打给我"); // true
// 正确:整个字符串必须刚好是手机号
/^1\d{10}$/.test("我的号码是13800138000,请打给我"); // false
另外 ^ 和 $ 默认只匹配字符串的首尾。加上 m 标志后,它们会匹配每一行的首尾:
js
const text = "第一行\n第二行";
text.match(/^.*行/g); // ["第一行"]
text.match(/^.*行/gm); // ["第一行", "第二行"]
性能陷阱:灾难性回溯
这是正则里唯一可能把服务打挂的问题。当多个量词可以匹配同一段文本时,引擎会尝试所有组合:
js
// 危险:嵌套量词,输入不匹配时会指数级回溯
/^(a+)+$/
// 输入 25 个 a 加一个 b,浏览器会卡死几秒
避免方法有三条:
- 不要写嵌套量词,
(a+)+改成a+ - 尽量用具体字符集替代
.,.*改[^\n]* - 限制输入长度,这是最省事的一道保险
调试方法
正则不要一次写对,要边写边验。我的流程是:
- 准备几条应该匹配和应该不匹配的样本
- 从最简单的部分开始,逐步加规则
- 每加一次就在样本上跑一遍,确认没破坏前面的
js
const cases = [
["13800138000", true],
["1380013800", false], // 少一位
["23800138000", false], // 不以 1 开头
];
const re = /^1\d{10}$/;
cases.forEach(([input, expected]) => {
const actual = re.test(input);
if (actual !== expected) console.log("失败:", input);
});
把这段测试留在代码里,以后改正则时还能防回归。
小结
正则的实用心法是三句话:能用字符串方法就别用正则;写完一定加锚点;永远给输入长度设上限。
做到这三条,正则就从「看得懂但不敢改」变成了一个可靠的工具。
#编程基础#正则表达式