字符匹配
正则表达式是匹配模式,要么匹配字符,要么匹配位置。
模糊匹配
横向模糊匹配
横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况。
其实现的方式是使用量词,譬如 {m,n},表示连续出现最少 m 次,最多 n 次。
正则 /ab{2,5}c/ 表示匹配这样一个字符串:第一个字符是 "a",接下来是 2 到 5 个字符 "b",最后是字符 "c"。
1 | let regex = /ab{2,5}c/g; |
match()方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
g是正则的一个修饰符,表示全局匹配,即在目标字符串中按顺序找到满足匹配模式的所有子串,强调的是“所有”,而不只是“第一个”。
纵向模糊匹配
纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。
其实现的方式是使用字符组,譬如 [abc],表示该字符是可以字符 "a"、"b"、"c" 中的任何一个。
/a[123]b/ 可以匹配如下三种字符串: "a1b"、"a2b"、"a3b"。
1 | let regex = /a[123]b/g; |
字符组
范围表示法
如 [123456abcdefGHIJKLM],可以写成 [1-6a-fG-M],即用连字符 - 来省略和简写。
要匹配
"a"、"-"、"z"这三者中任意一个字符,可以写成[-az]或[az-]或[a\-z],即要么放在开头,要么放在结尾,要么转义。
排除字符组
如 [^abc],表示的是一个除了 "a"、"b"、"c"之外的任意一个字符。字符组的第一位放 ^(脱字符),表示求反的概念。
当然,也可以使用范围表示法。
常见的简写形式
| 字符组 | 含义 |
|---|---|
\d |
表示 [0-9]。记忆方式:其英文是 digit(数字)。 |
\D |
表示 [^0-9]。 |
\w |
表示 [0-9a-zA-Z_]。记忆方式:其英文是 word,也称单词字符。 |
\W |
表示 [^0-9a-zA-Z_]。 |
\s |
表示 [ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:其英文是 space。 |
\S |
表示 [^ \t\v\n\r\f]。 |
. |
表示 [^\n\r\u2028\u2029]。通配符,表示几乎任意字符,换行符、回车符、行分隔符和段分隔符除外。 |
如果要匹配任意字符可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个。
量词
简写形式
| 量词 | 含义 |
|---|---|
{m,} |
表示至少出现 m 次。 |
{m} |
等价于 {m,m},表示出现 m 次。 |
? |
等价于 {0,1},表示出现或者不出现。 |
+ |
等价于 {1,},表示出现至少一次。 |
* |
等价于 {0,},表示出现任意次,有可能不出现。 |
贪婪匹配与惰性匹配
1 | let regex = /\d{2,5}/g; |
正则 /\d{2,5}/ 表示数字连续出现 2 到 5 次,会匹配 2 位、3 位、4 位、5 位连续数字,其是贪婪的,会尽可能匹配多的位数。
1 | let regex = /\d{2,5}?/g; |
惰性匹配,就是尽可能少的匹配位数,正则 /\d{2,5}?/ 表示虽然出现 2 到 5 次都行,但当 2 个就够的时候,就不再往下尝试了。
通过在量词后面加个 ? 就能实现惰性匹配。
多选分支
多选分支可以支持多个子模式任选其一。
具体形式如下:/p1|p2|p3/,其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示匹配其中任何一个模式即可。
分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。
1 | let regex = /good|goodbye/g; |
匹配字符,无非就是字符组、量词和分支结构的组合使用。
位置匹配
位置
位置(锚)是相邻字符之间的位置。
位置可以替换成字符。
在 ES5 中,共有 6 个锚:^、$、\b、\B、(?=p)、(?!p)。
^ 和 $
^(脱字符)匹配开头,在多行匹配中匹配行开头。
$(美元符号)匹配结尾,在多行匹配中匹配行结尾。
多行匹配模式时(有修饰符 m),会匹配每一行。
1 | let result = 'I\nlove\njavascript'.replace(/^|$/gm, '#'); |
\b 和 \B
\b 是单词边界,具体就是 \w 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置和 \w 与 $ 之间的位置。
\B 就是 \b 的反面的意思,非单词边界,具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置。
(?=p) 和 (?!p)
(?=p),其中 p 是一个子模式,整体表示匹配模式 p 的字符的前面的位置。
1 | let result = 'hello'.replace(/(?=l)/g, '#'); |
而 (?!p) 就是 (?=p) 的反面意思。
二者的学名分别是 positive lookahead 和 negative lookahead,中文翻译分别是正向先行断言和负向先行断言。
ES5 之后的版本,会支持 (?<=p) (positive lookbehind) 和 (?<!p)(negative lookbehind)。
位置的特性
位置可以理解成字符之间的空字符 "",且在两个字符之间可以有多个。
1 | let result = /(?=he)^^he(?=\w)llo$\b\b$/.test('hello'); |
括号
括号提供了分组,便于我们引用它。
引用某个分组会有两种情形:在 JavaScript 里引用它和在正则表达式里引用它。
分组和分支结构
强调括号内的正则是一个整体,即提供子表达式。
分组
正则 /a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/,其中括号提供分组功能,使量词 + 作用于 "ab" 这个整体。
分支结构
1 | let regex = /^I love (JavaScript|Regular Expression)$/; |
如果去掉括号,则匹配的字符串是 "I love JavaScript" 和 "Regular Expression"。
分组引用
括号的一个重要作用,有了它,就可以进行数据提取以及更强大的替换操作。
提取数据
1 | let regex = /(\d{4})-(\d{2})-(\d{2})/; |
match返回一个数组,第一个元素是整体匹配的结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。另外,正则表达式若有修饰符g,match返回只包含整体匹配结果而不包含分组匹配内容的数组。
另外也可以使用正则实例对象的 exec 方法。
1 | let regex = /(\d{4})-(\d{2})-(\d{2})/; |
使用
exec(), 不论正则是否带g修饰符,只要没有分组,其返回只包含一个匹配结果的类数组。如果带g修饰符,则可以通过反复调用exec()来获取下一个包含匹配结果的类数组,并且匹配下标index会更新至下一个匹配结果的开头在字符串中的位置,当调用时没有匹配结果时返回null。如果不带g修饰符,则反复调用都将返回包含第一个匹配结果的类数组。
也可以使用构造函数的全局属性 $1 至 $9 来获取。
1 | let regex = /(\d{4})-(\d{2})-(\d{2})/; |
替换
把 yyyy-mm-dd 格式替换成 mm/dd/yyyy 格式。
1 | let regex = /(\d{4})-(\d{2})-(\d{2})/; |
反向引用
除了可以使用相应的 API 来引用分组,也可以在正则本身里引用分组,但只能引用之前出现的分组,即反向引用。
1 | let regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; |
\1 表示引用之前的那个分组 (-|\/|\.),不管它匹配到什么(比如 -),\1 都匹配与分组匹配到的同样的具体的某个字符。
以此类推,\2 和 \3 分别指代第二个和第三个分组。
括号嵌套
以左括号(开括号)为准。
1 | let regex = /^((\d)(\d(\d)))\1\2\3\4$/; |
\10
\10 表示的是第 10 个分组而非 \1 和 0。
如果真要匹配
\1和0的话,请使用(?:\1)0或者\1(?:0)。((?:)表示非捕获分组,匹配的值不会保存起来)
引用不存在的分组
当在正则里引用了不存在的分组时,正则不会报错,只是去匹配反向引用的字符本身。
分组后有量词
分组后若有量词,分组最终捕获到的数据是最后一次的匹配,对于反向引用也是如此。
1 | let regex = /(\d)+ \1/; |
非捕获括号
如果只想使用括号最原始的功能,并不想引用它,可以使用非捕获括号 (?:p) 和 (?:p1|p2|p3)。