# 正则真烦人
可能不止我一个程序员会对正则表达式这种让人头大的东西产生厌恶吧,这种东西用的没有日常框架代码多,但总有那么一刻需要,因为这个东西拿来处理字符串匹配的时候是真的简单到家了,不仅如此,用它可以干脆利落地干掉绝大一部分字符串的算法问题,还记得有一次面字节的时候就被一道简单的正则算法题难到了,整个算法只用两三行足以搞定,而我绕了一大圈甚至还没 AC,真是麻了。
# 字符匹配
# 字符串
正则的使用非常简单,绝大多数直接用字面量形式命名即可(应该没人会用新建对象吧),组成则是分为字符和元字符,打个比方
1 | let rex = /a/ |
- 这个就是一个简单的正则表达式了,由 // 括起来的内容里,这里匹配你所要匹配的字符串,如 apple 中匹配 a 字符,或是匹配 app,但只匹配一次就终止后续匹配,有点类似数组的 find 方法
- 若是要匹配特殊字符,那么我们需要
\
作为转义字符,如\,
表示逗号,
- 转义字符除了特殊字符转义外,还能用作转码,常用的转码有
字符 | 正则 | 英文 |
---|---|---|
换行符 | \n | new line |
换页符 | \f | form |
回车符 | \r | return |
空白符 | \s | space |
制表符 | \t | table |
垂直制表符 | \v | vertical table |
回退符 | [\b] | backspace |
# 字符集
- 有时候我们需要匹配字符不是固定的单个,而是一些字符,如我需要的字符可能是 a-z 小写字母中的一个,那么就会用到字符集,字符集是用 [] 包裹,类似数组,[123] 就是匹配 1 或 2 或 3,当然也可以直接写 [1-3], 同样的支持 [0-9],[a-z],[A-Z]
- 另外的,字符集还可以用
^
来进行表示非,如 [^0-9] 表示非数字
除了枚举表示,我们还可以用下面的一些元字符来替代匹配
匹配区间 | 正则 | 备注 |
---|---|---|
非换行字符 | . | 换行就是结束。 |
单个数字 [0-9] | \d | digit |
非数,除了 [0-9] | \D | not digit |
包括下划线在内的单个字符,[A-Za-z0-9_] (注意这里包括了 0-9) | \w | word |
非单字字符 | \W | not word |
匹配空白字符,包括空格、制表符、换页符和换行符 | \s | space 译空白 |
匹配非空白字符 | \S | not space |
# 位置匹配
正则不仅可以实现简单的字符匹配,还可以匹配字符串中字符之间的位置,匹配这些位置能够帮助字符串进行分隔处理等作用。
下面是位置的一些表示
字符 | 正则 | 备注 |
---|---|---|
行首 | ^ | 尖头为首 |
行末 | $ | 赚钱是一切的尽头 |
单词边界 | \b | boundary,表示 ^ 与 /w 之间,/w 与 / W 之间,/w 与 $ 之间 |
非单词边界 | \B | not boundary |
# 位置处理
比如我们可以用 /^/
或 /$/
给 APP
单词插入字符
1 | 'APP'.replace(/$/,'LE') //APPLE |
# 匹配单词
当我们在一段话中需要匹配 apple
这个单词时,用 /apple/
可以匹配到,但很可能还会匹配到我们不需要的单词,如 pineapple
,这时候我们需要给字符串加一个边界,变成一个单词
单词的匹配需要用 \b
,如 /\bapple\b\/
会匹配 apple
这个单词时
# 匹配整行
如果是要匹配一个段落,我们需要用到 ^
和 $
(表示行首和行末),(这里的 ^ 并不是非的意思,非要在字符集中才算非,这里表示行首),例如 I am Iron Man
中,我们可以这样写 /^I am Iron Man$/
特别注意:大多时候文章并非一行,所以我们需要在匹配时选择多行匹配 m
, 写成 /^I am Iron Man$/m
# 常用模式
学完字符,我们列出一些模式匹配
常用的匹配模式有全局匹配 g
,忽略大小写 i
和多行模式 m
, 位于正则表达式最后即可,如 /ABC/g
, /ABC/gim
字符 | 正则 | 备注 |
---|---|---|
全局模式 | g | global |
多行模式 | m | multiple line |
忽略大小写 | i | ignore case |
# 连续匹配
目前为止我们已经能够单个搜索到我们想要的字符了,但是我们只能搜索出一次便停止搜索,当然我们可以用 g 进行全局搜索,但是如果要指定次数呢?
# 或有或无
?
可以表示字符或有或无,如 color
和 colour
要想同时匹配两者,可以写成 /colou?r/
,使 u
或有或无
# 0 或无限
*
可以匹配 0 次或是无数次,但是不同于全局匹配这个相当于从头开始,撞到与匹配不符合的就停下,有可能一次都没匹配到,如: apple
我要匹配 /p*/
则匹配为 0 次,匹配 /a*/
则匹配为 1 次
# 至少一次
+
匹配至少一次,不同于 *
, +
必须要撞到匹配的字符一次才会停下,如: apple
我要匹配 /p+/
则匹配为 2 次,匹配 /a*/
则匹配为 1 次。
# 特定次数
{}
可以表示特定次数,可以说是最好用的循环了
{x}: x 次
{min, max}: 介于 min 次到 max 次之间
{min,}: 至少 min 次
{0, max}: 至多 max 次
# 前缀后缀匹配
# 固定后缀匹配前缀
当我们需要前缀查找时可以使用 ?=
和 ?!
来匹配前缀,如 happy happily happen
中,我们可以使用 /happ(?=ily)/
匹配到 happily
中的 happ
, 用 /happ(?!ily)/
匹配到 happy
和 happen
中的 happ
我们还可以使用 /?=ily/
和 /?!ily/
来直接匹配位置
# 固定前缀匹配后缀
当我们需要后缀查找时可以使用 ?<=
和 ?<!
来匹配后缀,如 apple people
中,我们可以使用 /(?<=ap)ple/
匹配到 apple
中的 ple
, 用 /(?<!ap)ple/
匹配到 people
和 people
中的 ple
同样的,该方法也可用于匹配位置
字符 | 正则 | 备注 |
---|---|---|
固定后缀找前缀 | ?= | 后缀是否等于 |
固定非此后缀的字符串找前缀 | ?! | 后缀是否不等于 |
固定前缀找后缀 | ?<= | 前缀是否等于 |
固定非此前缀的字符串找后缀 | ?<! | 前缀是否非等于 |
# 练习
实现千分位的数字格式化
1 | '123456789'.replace(/(?!^)(?=(\d{3})+$)/g,',') //123,456,789 |
?=(\d{3})+
用于匹配每三个连续数字前的那个位置,而末尾的 $
则使其能从末尾开始匹配(只匹配最后匹配到的那一个位置,若无 $ 则从头匹配第一个), g
全局匹配多个位置, ?!^
用于匹配非开头的位置,