除换行符外的任意一个字符。
加 s(dotAll)标志后连换行也匹配。没有 s 标志时,惯用写法是 [\s\S]。
除换行符外的任意一个字符。
加 s(dotAll)标志后连换行也匹配。没有 s 标志时,惯用写法是 [\s\S]。
方括号里任意一个字符。
匹配 a 或 b 或 c
不是方括号里那些字符的任意一个字符。
^ 只有紧跟在 [ 后面才表示取反,写在别处就是普通的 ^ 字符。
字符范围。可以连写多段,如 [a-zA-Z0-9]。
一个数字,等价于 [0-9]。
一个非数字字符。
一个单词字符,等价于 [A-Za-z0-9_]。
默认不包含中文 —— 用 \w 去匹配中文用户名一定会漏。中文要用 \p{Script=Han} 或 [\u4e00-\u9fa5]。
一个非单词字符。
一个空白字符:空格、制表、换行、回车、换页、垂直制表。
JS 里 \s 还包含不换行空格 \u00a0 和各种 Unicode 空格,从网页复制来的文本里经常藏着它们。
一个非空白字符。
制表符 / 换行符 / 回车符。
Windows 换行是 \r\n,按行切分时写 /\r?\n/ 才两边都吃得下。
按两位十六进制指定字符,如 \x41 是 A。
按四位十六进制指定 UTF-16 码元,如 \u4e2d 是「中」。
按码点指定字符,可超出 BMP(如 emoji)。
任意字符,包括换行。
没有 s 标志时跨行匹配的标准写法。[\d\D]、[\w\W] 同理。
或:两边任选其一。
优先级最低,abc|def 是「abc 或 def」而不是「ab(c 或 d)ef」。要限定范围必须加括号。
转义:让元字符 . * + ? ( ) [ ] { } ^ $ | \ / 变回普通字符。
POSIX 字符类。
前面的元素出现 0 次或多次。
前面的元素出现 1 次或多次。
前面的元素出现 0 次或 1 次。
恰好 n 次。
\d{4} 匹配四位数字
至少 n 次,上不封顶。
n 到 m 次。
逗号后面不能有空格,{2, 5} 在 JS 里会被当成五个普通字符。
惰性(非贪婪)量词:在能匹配的前提下尽量少取。
量词默认贪婪。抓 <a>...</a> 里的内容要写 <a>(.*?)</a>,写成 .* 会一路吃到最后一个 </a>。
独占量词:匹配后不回溯,用于避免灾难性回溯。
字符串开头。加 m 标志后变成「每一行的开头」。
字符串结尾。加 m 标志后变成「每一行的结尾」。
JS 的 $ 不像 Python 的 $ 那样容忍结尾多一个换行,处理文件内容时容易差这一个字符。
单词边界:\w 与非 \w 之间的位置。
因为定义来自 \w,所以中文两侧不算边界。\b中文\b 这种写法多半不是你想要的效果。
非单词边界。
\B(?=(\d{3})+(?!\d)) 用来插千分位逗号
字符串真正的开头 / 结尾(不受 m 标志影响)。
上一次匹配结束的位置。
捕获组:既分组,又把匹配到的内容存起来供引用。
非捕获组:只分组,不占用组编号。
只是想用括号限定 | 的范围时用它,组编号不会被打乱,引擎也少做一点记录。
命名捕获组,用名字而不是编号取值。
JS 里通过 match.groups.name 取。比数第几个括号可靠得多,加一层括号不会全盘错位。
反向引用:要求这里重复出现第 n 组匹配到的内容。
(\w)\1 匹配 aa、bb 这种连续重复字符
按名字反向引用命名捕获组。
内联修饰符:从此处起忽略大小写。
先行肯定:右边必须能匹配,但不消耗字符。
\d+(?=元) 匹配「100元」里的 100,不含「元」
先行否定:右边必须匹配不上。
\d+(?!元) 匹配后面不是「元」的数字
后行肯定:左边必须能匹配,不消耗字符。
后行否定:左边必须匹配不上。
多个先行断言叠加,用来表达「同时满足若干条件」。
密码强度校验的标准写法:三个断言各查一类字符,最后的 .{8,} 才是真正消耗字符的部分。
global:找出所有匹配,而不是第一个就停。
带 g 的正则对象有 lastIndex 状态,反复用同一个对象调 test() 会出现「一次真一次假」。要么每次新建,要么用完置 lastIndex = 0。
ignoreCase:忽略大小写。
multiline:让 ^ 和 $ 匹配每一行的首尾。
dotAll:让 . 也能匹配换行符。
unicode:按码点处理,启用 \p{...} 和 \u{...}。
不加 u 的话,一个 emoji 会被当成两个字符,.{1} 只能吃掉半个,结果是乱码。
unicodeSets:u 的增强版,字符类支持交集 && 和差集 --。
sticky:只从 lastIndex 处开始匹配,不往后找。常用于写词法分析器。
hasIndices:结果里额外带上每个捕获组的起止位置(match.indices)。
替换串里代表整个匹配到的内容。
替换串里引用第 n 个捕获组。
把 /(\d+)-(\d+)/ 替换成 $2-$1 即可交换两段
替换串里引用命名捕获组。
替换串里表示一个字面的 $ 符号。
匹配位置之前的全部内容。
匹配位置之后的全部内容。
任意字母(任何语言)。
任意数字类字符,含全角数字和其他文字体系的数字。
任意标点,含中文全角标点。
汉字(含扩展区的生僻字)。
比 [\u4e00-\u9fa5] 全面得多,「𠮷」这类扩展区汉字只有它认。
平假名。Katakana 是片假名,Hangul 是谚文。
大写 P 表示取反:不是字母的字符。
常用汉字区间,不需要 u 标志。
这只是 CJK 统一表意文字基本区的一段,扩展区汉字和部分生僻字不在里面。老代码里最常见,但不是最准的写法。
零宽空格 / BOM / 不换行空格。
从网页或 Word 复制来的文本里的隐形字符,肉眼看不出来但会让 trim() 和 === 全部失效。清洗文本时记得一起去掉。
中国大陆手机号(宽松校验)。
只查「1 开头 + 第二位 3–9 + 共 11 位」。号段一直在增加,任何号段白名单写死都会过时;真要确认号码有效只能发一条短信。
邮箱(实用级校验)。
邮箱不可能用正则校验干净 —— 符合 RFC 5322 的完整正则有几百个字符,而且仍然判断不了地址是否真的存在。生产环境唯一可靠的办法是发验证邮件,正则只用来挡明显的手滑。
18 位居民身份证号(格式校验)。
只校验格式。最后一位是按 ISO 7064:1983 MOD 11-2 算出来的校验位,正则算不了 —— 需要真校验请用本站的「身份证解析」工具。
IPv4 地址(每段 0–255)。
不要图省事写成 \d{1,3}(\.\d{1,3}){3},那样 999.999.999.999 也能过。
日期 YYYY-MM-DD。
正则管不了「这个月有没有 31 号」,2 月 30 日照样通过。日期合法性要交给 Date 或日期库判断。
24 小时制时间 HH:MM:SS。
HTTP/HTTPS 网址(宽松)。
浏览器里直接用 new URL(str) 试着构造并 catch,比任何正则都准。
十六进制颜色值,三位或六位。
中国大陆邮政编码(六位,不以 0 开头)。
统一社会信用代码(18 位)。
字符集刻意排除了 I、O、S、V、Z ——标准里就没有这几个字母,避免和数字 1、0、5、2 混淆。
千分位插入点,配合 replace 加逗号。
匹配的是「位置」而不是字符,所以直接 replace 成 "," 就行。
首尾空白,配合 g 标志去掉两端空格。
在 JS 里直接用 String.prototype.trim() 就够了,这条主要用在只能写正则的地方(编辑器查找替换、日志过滤规则)。
三个以上连续换行,用于把多余空行压成一个。
强密码:至少 8 位,含大小写字母、数字和符号各一。
连续重复 3 次以上的同一个字符,用于挡「aaaaaa」这类垃圾输入。
正则语法速查收了 80 条语法,分 9 类,全部带中文释义,点语法即复制。这页解决的是「查语法」,验证表达式请去正则测试器或 Regex Pro。
正则不是一种语言,是一族方言。JavaScript、PCRE、Python、Go 的 RE2、Java 各有各的取舍,而中文搜索结果里绝大多数正则教程是按 PCRE 写的。差异最常见的几处:
(?<=...):JS 到 ES2018 才有,Safari 直到 16.4 才支持\A \z:JS 完全没有(?i):JS 不支持,标志只能挂在正则末尾*+:JS 不支持标签只打在真正有差异的条目上,没标签的就是各方言一致的通用语法。
页面里的 15 条常用模式(手机号、身份证、IPv4、日期、强密码……)每条都写明了它校验到什么程度。这点比模式本身重要:
正则是过滤器,不是验证器。 它擅长挡住明显的手滑,不擅长确认一个值真实有效。
多半是方言不同。 搜索结果里的正则大多按 PCRE(PHP / Perl)或 Python 的口径写,而浏览器和 Node 跑的是 JavaScript 引擎,几处常见的不兼容:\A \z \Z 这些锚点 JS 完全不认,要用 ^ $ 配合;内联修饰符 (?i) 不支持,标志只能写在正则末尾;独占量词 *+ 不支持;POSIX 字符类不支持。本页凡是有差异的条目都打了「方言差异」标签,照着改就行。
量词默认贪婪,会尽量多吃。 用 <a>(.*)</a> 去抓一段含多个链接的 HTML,它会从第一个 <a> 一路匹配到最后一个 </a>,中间全被吞掉。在量词后面加 ? 变成惰性的 (.*?),才会在遇到第一个 </a> 时停下。经验法则:只要你的模式里有「直到遇见某个结束标记为止」的意思,量词就该是惰性的。
不能,而且这不是写得够不够巧的问题。 HTML 和 JSON 都允许任意层级的嵌套,而正则(在没有递归扩展的情况下)表达不了「配对的括号」这种结构——这是形式语言层面的限制,不是技巧不够。实践后果很具体:注释里的假标签、属性值里的尖括号、自闭合标签、嵌套同名元素,每一样都能让看起来没问题的正则给出错误结果。HTML 用 DOMParser,JSON 用 JSON.parse。正则只适合在已经解析好的文本片段里做局部匹配。
嵌套量词遇上匹配失败时,引擎会尝试指数级数量的组合,页面直接卡死。 典型形状是 (a+)+$ 或者 (\s*\w+)*$ 这类「量词套量词」,拿一个长字符串去跑,回溯路径的数量随长度翻倍增长,几十个字符就能让浏览器主线程停几秒到几分钟。三条规避办法:① 别让两个量词嵌套;② 用具体的字符类代替 .,让失败尽早发生;③ 处理用户输入的正则时,放进 Web Worker 并设超时——本站的 Regex Pro 就是这么做的。
因为它的定义是 ASCII 的 [A-Za-z0-9_],就这么定死的。 同理,单词边界 \b 是按「单词字符和非单词字符的交界」定义的,中文两侧不构成边界,写 \b中文\b 基本不会是你想要的效果。要匹配汉字有两种写法:\p{Script=Han}(需要 u 标志,覆盖扩展区生僻字,推荐)和 [一-龥](不需要标志,但只覆盖基本区的一段,老代码里最常见)。
因为带 g 的正则对象有 lastIndex 状态,它会记住上次匹配到哪。 复用同一个正则对象反复调 test() 或 exec(),第二次会从上次结束的位置继续找,找不到就返回 false 并把 lastIndex 归零,于是下一次又成功——表现为真假交替。两种解法:每次现场新建正则字面量,或者在复用前手动 re.lastIndex = 0。把正则提到模块顶层当常量缓存时最容易踩到这个。