(未完成) JavaScript 正则表达式笔记
各种编程语言里都有正则,可恶的是它们提供的 API 还经常不一致,这就导致了我这种记性不好的人总会记混。目前在写前端,准备彻底整理一下 JavaScript 的正则表达式。
JavaScript 的正则来自于 Perl,还在 Perl 正则的基础上做了一定的改进,用起来还是挺不错的,而且语法和 API 并不复杂。
正则表达式的定义
在 JavaScript 中,正则有两种创建方式:
- 使用直接量语法创建
var patten = /[Jj]ava[Ss]cript$/;
- 使用 RegExp() 构造函数创建。
var patten = new RegExp('[Jj]ava[Ss]cript$');
第一种方式写起来比较简单,但是有时候我们可能需要在程序运行过程中动态的将一个字符串转为正则表达式,就需要使用第二种方式了。
正则表达式语法
基本语法
- 正则表达式是一个字符序列,大多数字符(如字母和数字)都是按照字符本身的意思进行匹配的,比如
/java/
就可以匹配所有包含'java'
子串的字符串。 - 那么 JavaScript 的正则中如何来匹配一些特殊的字符呢,他们是通过反斜线()为前缀的转义字符表示的,如下表:
字符 | 匹配 |
---|---|
字符和数字字符 | 自身 |
\o | NUL 字符(u0000) |
\t | 制表符(u0009) |
\n | 换行符(u000A) |
\v | 垂直制表符(u000B) |
\f | 换页符(u000C) |
\r | 回车符(u000D) |
\xnn | 由十六进制数 nn 指定的拉丁字符,例如 x0A 等价于 n |
\uxxxx | 由十六进制数 xxxx 指定的 Unicode 字符,例如 u0009 等价于 t |
\cX | 控制字符 ^X,例如 cJ 等价于换行符 n |
- 在正则表达式中,有一些字符是有特殊含义的,它们分别是:
^ $ . * + ? = ! : | \ / ( ) [ ] { }
字符类
在 JavaScript 正则中可以把直接量字符放到方括号里组成字符类,一个字符类可以匹配包含或者不包含它的任意一个字符。
/[abc]/
可以匹配字符串'a'
或'b'
或'c'
。/[^abc]
可以匹配不包含a
、b
、c
任意一个字符的字符串,如hello
。
- 在字符类中可以使用连词符
-
表示某个范围内的字符。如/[a-z]/
可以匹配所有的小写字母。 - 由于有些字符类非常常用,所以在 JavaScript 的正则语法中,可以使用特殊的转义字符表示一些字符类,如下表:
字符 | 匹配 |
---|---|
[...] | 方括号内中的任意字符 |
[^...] | 不在方括号内的任意字符 |
. | 除了换行符、回车符和其他 Unicode 终止符之外的任意字符 |
\w | 任何字母和数字字符,等价与 [a-zA-Z0-9] |
\W | 任何不是字母和数字的字符,等价于 [^a-zA-Z0-9] |
\s | 任何 Unicode 空白符,包括 \n 和 \r |
\S | 任何非 Unicode 空白符 |
\d | 任何 ASCII 数字,等价于 [0-9] |
\D | 除了 ASCII 数字之外的任何字符,等价于 [^0-9] |
[\b] | 退格直接量 |
可以发现,这些转义的字符类中,大写字母都是小写字母的补集。另外 [\b]
是一个特例,它必须放在 []
里面才是字符类,否则它用来指定匹配位置(后面介绍)。
重复
从前面介绍的语法中可以知道,表示一位数字的正则可以写成 /\d/
,那两位数字就是 /\d\d/
,三位数字就是 /\d\d\d/
,那么一百位数字呢?
正则中有一种语法可以用来描述重复的匹配,见下表。
语法 | 含义 |
---|---|
{n,m} | 重复匹配至少 n 次,但是不超过 m 次(左右闭区间) |
{n,} | 重复匹配最少 n 次 |
{n} | 重复匹配 n 次 |
{n,} | 重复匹配最少 n 次 |
{n} | 重复匹配 n 次 |
? | 匹配前一次 0 次或者 1 次,也就是说前一项是可选的,等价于 {0,1} |
+ | 匹配前一项 1 次或多次,等价于 {1,} |
* | 匹配前一项 0 次或多次,等价于 {0,} |
下面是一些例子:
/\d{2,4}/ // 匹配 2~4 个数字
/\w{3}\d?/ // 精确匹配三个字母或数字后再匹配一个可选的数字
/\s+java\s+/ // 匹配前后带一个或多个空格的字符串 'java'
/[^(]*/ // 匹配一个或多个非左括号的字符
非贪婪匹配
上面介绍的重复语法是尽可能多的匹配,我们称之为贪婪匹配,比如使用 /\d+/
来匹配 '12345'
的话,会匹配整个字符串,如果使用 \d{1,3}
来匹配 '12345'
的话会得到 '123'
。
我们可以同样可以使用正则表达式来进行非贪婪的匹配,即匹配尽可能少的字符,只需要在待匹配的字符后面加一个问号即可:比如 /\d??/, /\d+?/, /\d*?/, /\d{1,5}?/
等。对于 '12345' 这个字符串来匹配,它们得到的结果分别是:'', '1', '', '1'
。
应该注意这样的情况:使用正则表达式 /a+?b/
来匹配字符串 'aaab' 时,你可能期望它匹配 'ab',实际上它匹配了 'aaab',这是因为,正则表达式总是有限选择第一个能匹配的位置,显然,这个字符串在第 0 个字符的时候就匹配了整个正则表达式,所以返回 'aaab'。
选择、分组和引用
正则表达式语法还包括指定选择项、子表达式分组和引用前一子表达式的特殊字符。
字符 |
用来分隔供选择的字符,比如 /ab|cd|ef/ 可以用来匹配 'ab'
或者 'cd'
或者 'ef'
。可以发现,|
和 ? + *
等重复语法不同,|
的优先级较低,也就是说,重复语法只会作用于前面的一个字符,而 |
会作用于前面的整个表达式。
正则表达式中的圆括号有多种作用,其中一个作用就是可以将几个项组合为一个单元,这个单元就可以通过重复语法 ? + *
,或者选择语法 |
来修饰了。比如 /java(script)?/
可以匹配字符串 'java'
也可以匹配字符串 'javascript'
。
圆括号的另一个作用是在完整的模式中定义子模式。当一个正则表达式成功的和目标字符串匹配时,可以从目标串中抽取出和圆括号中的子模式相匹配的部分。比如我们想检索一个或者多个小写字母后面跟随了多个数字的话,可以使用这样的正则表达式:/[a-z]+\d+/
,但是我们实际关心的是后面匹配的数字,那么就可以将匹配的数字部分放到括号里,/[a-z]+(\d+)/
,这样就可以从匹配结果中抽取数字了,在后面 JavaScipt 中正则的使用中会有讲解。
圆括号还有一个作用就是允许在同一个正则表达式后面引用前面的子表达式。这个是通过在字符 \
后面加一位或多位数字实现的。比如,\1
就可以引用第一个圆括号里面的表达式,\3
就是第三个圆括号里面的表达式。可以根据参与计数的左括号的位置判断是第几个子表达式。
语法 | 含义 |
---|---|
| | 选择,匹配该符号左面的表达式或者右面的表达式 |
(...) | 组合,将几个项组合为一个单元,这个单元就可以通过重复语法,或者 | 来修饰了。 |
(?:...) | 只组合,把项组合到一个单元,但是不记忆与该项匹配的字符 |
n | 和第 n 个分组第一次匹配的字符相匹配 |
{n} | 重复匹配 n 次 |
? | 匹配前一次 0 次或者 1 次,也就是说前一项是可选的,等价于 {0,1} |
+ | 匹配前一项 1 次或多次,等价于 {1,} |
* | 匹配前一项 0 次或多次,等价于 {0,} |