Regex | 正则表达式的学习



字面量字符和元字符

在正则表达式之中,某个字符只表示它字面的含义(比如/a/匹配 a),那么它们就叫做“字面量字符”(literal characters); 此外,还有一些字符有特殊含义,不代表字面的意思,称为“元字符”(metacharacters)

常见的元字符:

  1. 点字符
    点字符(.)匹配除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的任何单字符

  2. 位置字符
    位置字符用来提示字符所处的位置,主要有两个字符:^ 表示字符串的开始位置;$ 表示字符串的结束位置

1
2
3
4
5
6
7
8
9
// word 必须出现在开始位置
/^word/.test('word123') // true

// word 必须出现在结束位置
/word$/.test('new word') // true

// 从开始位置到结束位置只有word
/^word$/.test('word') // true
/^word$/.test('word word') // false
  1. 选择符
    竖线符号(|)在正则表达式中表示“或关系”(OR),即cat|dog表示匹配cat或dog
1
2
3
4
5
6
7
8
9
10
11
12
// 匹配 11 或 22
/11|22/.test('911') // true

// 匹配fred、barney、betty之中的一个
/fred|barney|betty/

// 选择符会包括它前后的多个字符
// 匹配 ab 或者 cd,ab 是绑在一块算的
/ab|cd/
// 可以用圆括号将选择的内容括起来
// a 和 b 之间空格或有一个制表符
/a( |\t)b/.test('a\tb') // true

转义符

正则表达式中那些有特殊含义的元字符,如果要匹配它们本身,就需要在它们前面要加上反斜杠

正则表达式中,需要反斜杠转义的,一共有12个字符:^ . [ $ ( ) | * + ? { \

如果使用 RegExp 方法生成正则对象,转义需要使用两个斜杠,因为字符串内部会先转义一次

1
2
3
4
5
6
7
8
// 加号需要转义
/1+1/.test('1+1') // false
/1\+1/.test('1+1') // true

// RegExp作为构造函数,参数是一个字符串
// 所以反斜杠自身需要被先转义一次
(new RegExp('1\+1')).test('1+1') // false
(new RegExp('1\\+1')).test('1+1') // true

特殊字符

特殊字符(不可打印) 表达
\cX 表示Ctrl-[X],其中的X是A-Z中任一个英文字母,用来匹配控制字符
[\b] 匹配退格键(U+0008),不要与\b混淆
\n 匹配换行键
\r 匹配回车键
\t 匹配制表符 tab(U+0009)
\v 匹配垂直制表符(U+000B)
\f 匹配换页符(U+000C)
\0 匹配null字符(U+0000)
\xhh 匹配一个以两位十六进制数(\x00-\xFF)表示的字符
\uhhhh 匹配一个以四位十六进制数(\u0000-\uFFFF)表示的 Unicode 字符

字符类

字符类(class)表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。

脱字符(^)和连字符(-)在字符类中有特殊含义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 匹配 abc 任意一个
/[abc]/.test('apple') // true


// 脱字符 ^
// 表示除了字符类之中的字符,其他字符都可以匹配
// 脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义

// [^xyz] 表示除了 x、y、z 之外都可以匹配
/[^abc]/.test('bbc news') // true 存在 abc 之外的值
/[^abc]/.test('bbc') // false 不存在 abc 之外的值

// 方括号内没有其他字符,即只有 [^],就表示匹配一切字符,
// 其中包括换行符。而元字符(.)是不包括换行符的
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']


// 连字符 -
// 对于连续序列的字符,连字符(-)提供简写形式,表示字符的连续范围
// 只有当连字号用在方括号之中,才表示连续的字符序列
[a-c] // [abc]
[0-9] // [0123456789]
[A-Z] // 表示 26 个大写字母

预定义模式

预定义模式指的是某些常见模式的简写方式

预定义 作用
\d 匹配 0-9 之间的任一数字,相当于 [0-9]
\D 匹配所有 0-9 以外的字符,相当于 [^0-9]
\w 匹配任意的字母、数字和下划线,相当于 [A-Za-z0-9_]
\W 除所有字母、数字和下划线以外的字符,相当于 [^A-Za-z0-9_]
\s 匹配空格(包括换行符、制表符、空格符等),相等于 [ \t\r\n\v\f]
\S 匹配非空格的字符,相当于 [^ \t\r\n\v\f]
\b 匹配词的边界
\B 匹配非词边界,即在词的内部
1
2
3
4
5
6
7
8
9
10
11
// \s 匹配空格,\w 匹配任意字母,* 将前一个字符重复
/\s\w*/.exec('hello world') // [" world"]

// \b 匹配 world 当作一个词
/\bworld/.test('hello world') // true
/\bworld/.test('hello-world') // true
/\bworld/.test('helloworld') // false

// \B 匹配 world 在词的内部
/\Bworld/.test('hello-world') // false
/\Bworld/.test('helloworld') // true

重复类

模式的精确匹配次数,使用大括号({})表示

1
2
3
4
5
6
{n}    // 表示恰好重复 n 次
{n,} // 表示至少重复 n 次
{n,m} // 表示重复不少于 n 次,不多于 m 次

/lo{2}k/.test('look') // true
/lo{2,5}k/.test('looook') // true

量词符

量词符用来设定某个模式出现的次数

量词符 作用
? 表示某个模式(前一个字符)出现 0 次或 1 次,等同于 {0, 1}
* 表示某个模式(前一个字符)出现 0 次或多次,等同于 {0,}
+ 表示某个模式(前一个字符)出现 1 次或多次,等同于 {1,}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// t 出现 0 次或 1 次
/t?est/.test('est') // true
/t?est/.test('test') // true

// t 出现 1 次或多次
/t+est/.test('test') // true
/t+est/.test('ttest') // true
/t+est/.test('est') // false

// t 出现 0 次或多次
/t*est/.test('test') // true
/t*est/.test('ttest') // true
/t*est/.test('tttest') // true
/t*est/.test('est') // true

贪婪模式

匹配到下一个字符不满足匹配规则为止(匹配最长的子串),这被称为贪婪模式,以上提到的三个量词符均为贪婪匹配

在量词符后再加 ?,则会对前一个字符进行最小可能匹配

最小匹配操作符 说明
*? 前一个字符0次或无限次扩展,最小匹配
+? 前一个字符1次或无限次扩展,最小匹配
?? 前一个字符0次或1次扩展,最小匹配
{m,n}? 扩展前一个字符m至n次 (含n) ,最小匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a+ 即重复 a 一次或多次,但此时返回匹配到最多的情况
var s = 'aaa';
s.match(/a+/) // ["aaa"]

// 使用 +?,则按最小可能匹配
var s = 'aaa';
s.match(/a+?/) // ["a"]

// * 使 b 重复 0 次或多次,贪婪匹配
'abb'.match(/ab*/) // ["abb"]
// *? 使 b 重复 0 次或多次,最小匹配
'abb'.match(/ab*?/) // ["a"]

// ? 使 b 重复 0 次或 1 次,贪婪匹配
'abb'.match(/ab?/) // ["ab"]
// ?? 使 b 重复 0 次或 1 次,最小匹配
'abb'.match(/ab??/) // ["a"]

修饰符

修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部,可以单个使用,也可以多个一起使用。

g 修饰符

默认情况下,第一次匹配成功后,正则对象就停止向下匹配了。g 修饰符表示全局匹配(global),加上它以后,正则对象将匹配全部符合条件的结果,每次都是从上一次匹配成功处,开始向后匹配,主要用于搜索和替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 不含 g 修饰符,每次都是从字符串头部开始匹配
var regex = /b/;
var str = 'abba';

regex.test(str); // true
regex.test(str); // true
regex.test(str); // true


// 使用 g 修饰符,每次从上次成功处开始向后匹配
var regex = /b/g;
var str = 'abba';

regex.test(str); // true 从左往右第 2 个成功
regex.test(str); // true 从第 3 个开始,成功
regex.test(str); // false 从第 4 个开始,失败

i 修饰符

默认情况下,正则对象区分字母的大小写,加上 i 修饰符以后表示忽略大小写(ignoreCase)。

1
2
3
// 使用 i 修饰符,就能匹配到 大写 ABC
/abc/.test('ABC') // false
/abc/i.test('ABC') // true

m 修饰符

m 修饰符表示多行模式(multiline),会修改 ^ 和 $ 的行为。默认情况下(即不加 m 修饰符时),^ 和 $ 匹配字符串的开始处和结尾处,加上 m 修饰符以后,^ 和 $ 还会匹配行首行尾,即 ^ 和 $ 会识别换行符(\n)

1
2
3
4
5
6
// 匹配 world 出现在结尾,如果有换行符,则 m 修饰符下才能识别
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true

// 匹配行首的 b
/^b/m.test('a\nb') // true

组匹配

正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 给一个词加括号,那么 + 就表示这个词重复了 1 次或多次
/fred+/.test('fredd') // true
/(fred)+/.test('fredfred') // true


// match 返回所有匹配的结果,前一个括号匹配 a,后一个匹配 c
'abcabc'.match(/(.)b(.)/) // ['abc', 'a', 'c']


// 可以使用 \n 引用括号匹配的内容,
// n 是从 1 开始的自然数,表示对应顺序的括号
// 第一个点就是 \1,第二个 \2
/(.)b(.)\1b\2/.test("abcabc") // true
/y(..)(.)\2\1/.test('yabccab') // true

// 括号还可以嵌套,\1 指向外层括号,\2 指向内层括号
/y((..)\2)\1/.test('yabababab') // true


// 匹配网页标签,圆括号匹配标签名,\1 就表示对应的闭合标签
var tagName = /<([^>]+)>[^<]*<\/\1>/;
tagName.exec("<b>bold</b>")[1]
// 'b'

非捕获组

(?:x) 称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一个括号是非捕获组,第二个括号为正常的组
'abc'.match(/(?:.)b(.)/) // ["abc", "c"]

// 两个括号均为非捕获组,则返回整体匹配内容,并不返回组匹配内容
'abc'.match(/(?:.)b(?:.)/) // ["abc"]


// 分解网址的正则表达式
// 正常匹配
// 选择 http 或 ftp,转义 //,
// 第一个括号 ([^/\r\n]+) 表示除反斜杠,回车,换行符的
// 其他任意字符一次或无限次,保证至少有一个字符
// 第二个括号 ([^/\r\n]*) 表示除回车,换行符的
// 其他任意字符一次或无限次,其后又加了 ?,
// 表示第二个组重复 0 次或 1 次
var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "http", "google.com", "/"]

// 非捕获组匹配
var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "google.com", "/"]

先行断言

x(?=y) 称为先行断言(Positive look-ahead),x 只有在 y 前面才匹配,y 不会被计入返回结果

1
2
3
4
/\d+(?=%)/   // 匹配后面跟着百分号的数字

// 匹配处于 c 前的 b,返回匹配结果
'abc'.match(/b(?=c)/) // ["b"]

先行否定断言

x(?!y) 称为先行否定断言(Negative look-ahead),x只有不在y前面才匹配,y不会被计入返回结果

1
2
3
4
/\d+(?!%)/   // 匹配后面跟的不是百分号的数字

// 匹配不处于 c 前的 b,返回匹配结果
'abd'.match(/b(?!c)/) // ["b"]

参考链接

网道- JavaScript教程 - JSON匹配规则