学完这篇,你能自己写出常用的校验、提取、替换正则,并看懂别人写的复杂表达式。
第一步:先搞清两种创建方式
const re1 = /abc/g; // 字面量,编译期解析,性能好,推荐
const re2 = new RegExp("abc", "g"); // 动态拼接时必须用这个
四个常用 flag:
g 全局匹配(不加只找第一个)
i 忽略大小写
m 多行模式,让 ^ $ 匹配每行首尾
s dotAll,让 . 能匹配换行符
注意:字面量里的内容不要再套引号,/"abc"/ 匹配的是带双引号的字符串。
第二步:记住这几个高频元字符
| 写法 | 含义 |
|---|
\d / \D | 数字 / 非数字 |
\w / \W | 字母数字下划线 / 其反 |
\s / \S | 空白 / 非空白 |
. | 任意字符(默认不含换行) |
[abc] | 其中任一字符 |
[^abc] | 除这些之外 |
量词:{n} 恰好 n 次、{n,m} 范围、+ 一次以上、* 零次以上、? 零或一次。
第三步:分组与捕获
圆括号既分组又捕获:
const m = "2024-06-01".match(/(\d{4})-(\d{2})-(\d{2})/);
// m[1]='2024' m[2]='06' m[3]='01'
分组多了容易数位置,用命名分组更清楚:
const m = "2024-06-01".match(/(?<y>\d{4})-(?<mo>\d{2})-(?<d>\d{2})/);
console.log(m.groups.y); // 2024
不想捕获就写 (?:...),能少占一个编号。
第四步:边界和断言
^ 开头、$ 结尾、\b 单词边界
(?=...) 先行断言:后面必须是它,但不消耗字符
(?<=...) 后行断言:前面必须是它
"100px 200em".match(/\d+(?=px)/); // ['100']
"¥88 $99".match(/(?<=¥)\d+/); // ['88']
注意:^...$ 忘加 m 时,整段有多行文字就永远匹配不上;而校验整串输入时,不加 ^ $ 会导致「部分匹配也算通过」,比如 /\d+/ 校验手机号,abc123 也会返回 true。
第五步:贪婪和懒惰
量词默认贪婪,尽可能多吃。加 ? 变懒惰:
"<p>a</p><p>b</p>".match(/<p>.*<\/p>/)[0]; // 吃到最后一个 </p>
"<p>a</p><p>b</p>".match(/<p>.*?<\/p>/)[0]; // 只吃第一个
抓 HTML 片段、日志切分时,忘记加 ? 是最常见的 bug 来源。
第六步:五个实战案例
1. 手机号校验(中国大陆)
const PHONE = /^1[3-9]\d{9}$/;
2. 千分位分隔
const format = n => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
format(1234567); // '1,234,567'
3. 驼峰转下划线
"userName".replace(/([A-Z])/g, "_$1").toLowerCase(); // user_name
4. 解析 URL 参数
const parseQuery = url => {
const q = url.split("?")[1] || "";
return Object.fromEntries(
[...q.matchAll(/([^&=]+)=([^&]*)/g)].map(([, k, v]) =>
[k, decodeURIComponent(v)]
)
);
};
matchAll 要求正则带 g,否则直接抛错。
5. 密码强度:至少 8 位,含字母和数字
const PWD = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$/;
两个先行断言分别检查「有没有字母」「有没有数字」,最后才消费字符。
第七步:三个必踩的坑
注意:带 g 的正则对象有 lastIndex 状态,test() 连续调用会在 true/false 之间跳。要么每次新建正则,要么手动 re.lastIndex = 0。String.prototype.match 配 g 时只返回整体匹配数组,拿不到分组,要分组用 matchAll 或去掉 g 用 exec。
注意:用户输入的字符串要拼进 new RegExp() 前先转义:str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),否则 ( 之类字符会直接让构造抛错。
注意:正则不是万能的。解析嵌套 HTML、复杂 JSON 请用 DOM / JSON.parse,正则只在结构简单稳定时可靠。
小结
- 静态规则用字面量,动态拼接用
new RegExp 并先转义
- 校验整串一定要
^...$,多行文本记得 m
- 要取分组就用命名分组
(?<name>),只分组不取值用 (?:)
- 匹配「最短一段」时别忘了懒惰量词
*?
g 正则有状态,注意 lastIndex 和 match / matchAll 的差别
- 建议先在控制台用
"测试字符串".match(/正则/) 试,再写进代码