字符匹配
正则表达式是匹配模式,要么匹配字符,要么匹配位置。
模糊匹配
横向模糊匹配
横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况。
其实现的方式是使用量词,譬如 {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)
。