在很多编程语言中如java
、PHP
、python
、JavaScript
等都可以使用正则表达式,但是不管在什么语言中,正则表达式的规则都是一样的。
正则表达式是独立的体系,与编程语言无关,唯一的关联就是不同语言中使用正则表达式规则的API
不同。
正则表达式主要对字符串进行操作:查找和替换。
在下列场景中经常运用到正则表达式:
- 表单数据的验证
- 代码高亮
- 爬虫
那么,接下来开始了解正则表达式,由于本人是前端开发出身,所以语法示例中是 javascript
,不过这对于学习正则表达式并没有太大的影响。
以下内容皆是《正则表达式必知必会》的读书笔记
1. 匹配单个字符
在此之前还是得先介绍下 javascript 中的正则表达式是怎样的、
在 javascript 中,正则表达式是书写在双斜杠之中的。
let reg = /he/; // 这个 /he/ 就是正则表达式。
1.1 匹配纯文本
let str = 'hello world'
let reg = /hello/;
console.log(str.match(reg)) // hello
字符串对象中的 match
函数会取出符合正则表达式规则的字符串。
上述代码中,正则表达式规则为hello
,是个纯文本,所以很直白的就只能匹配到字符串中的 hello 。如果字符串中没有 hello ,那么将匹配不到,返回 null 。
1.2 全局匹配
全局匹配就是不会只匹配一次,会从头到尾检索字符串,而不是查到一个符合的结果就停止了。
通过下面两段代码,可以看出全局和非全局的区别
// 非全局
let str = 'hello world, hello'
let reg = /hello/;
console.log(str.match(reg)) // hello
// 全局
let str = 'hello world, hello'
let reg = /hello/g;
console.log(str.match(reg)) // hello hello
/hello/g
这后面的 g
便是表示全局的意思,如果不加上 g 的话,那么只能匹配到一个 hello ,后面的 hello 就匹配不到了。
1.3 忽略大小写
一般情况下 /hello/
是无法匹配到 Hello
的,因为对大小写敏感,所以在必要时候可以在后面加上 i
来忽略大小写。
let str = 'Hello world, Hello'
let reg = /hello/i;
console.log(str.match(reg)) // Hello
如果要忽略大小写的同时进行全局匹配,那也很简单,加上 ig
即可。
let str = 'Hello world, Hello'
let reg = /hello/ig;
console.log(str.match(reg)) // Hello Hello
1.4 匹配任意字符
通过 .
这个特殊字符,可以匹配到任意的字符,包括 .
本身。
let str = 'hel, h5l, h55l, h.l'
let reg = /h.l/g;
console.log(str.match(reg)) // hel h5l h.l
// 为什么没有匹配到 h55l ,因为 . 只能匹配一个任意字符
1.5 转义字符
通过上面章节的学习,我们知道 .
可以匹配任意字符,但如果说我只是想要匹配到一个 h.l 怎么做呢?
可以利用反斜杠 \
进行转义,将 .
变成一个没有特殊意义的普通字符。
let str = 'hel, h5l, h55l, h.l'
let reg = /h\.l/g;
console.log(str.match(reg)) // h.l
凡是要匹配正则中特殊的字符,都需要用 \
。
2. 匹配一组字符
如果相匹配一组字符串中的 a 和 d,那么应该怎么做呢?
直接写 /a/g
虽然可以匹配到 a ,但是匹配不到 d 啊。
写 /ad/g
这匹配的又是 ad 这个整体。
写 /a./g
,这也不适合,会匹配到以 a 开头的任意字符串。
所以接下来便认识下 []
,给字符设置一个区间。
2.1 [] 的使用
[]
是限定了一个范围,在这个范围内匹配
let str = 'ac,bc,cc,dc,fc'
let reg = /[ad]c/g;
console.log(str.match(reg)) // ac dc
[ad]
就表示匹配 a 或 d ,其他则不匹配,因此整个正则表达式 /[ad]c/g
就是匹配 ac
或 dc
。
2.2 字符区间 [ - ]
这次的字符串是 ac,bc,cc,dc,fc, 5c, 8c
,我们要匹配到的内容是,第一个字符是任意一个小写字符,第二个字符是c。
通过上面章节的学习,我们想到的写法是 /[qwertyuiopasdfghjklzxcvbnm]c/g
,在 []
中的字符是没有所谓的顺序的,我就是把键盘上的字母依次敲了一遍。
这样写虽然可以达到我们的要求,但是太过麻烦了。
因此出现了 -
这个字符,它代表到
或至
的意思。
let str = 'ac,bc,cc,dc,fc, 5c, 8c'
let reg = /[a-z]c/g; // a到z
console.log(str.match(reg)); // ac,bc,cc,dc,fc,
a-z
就是 a 到 z ,同样还有其他的,比如 A-Z
,以及 0-9
。
注意:在 []
中想要匹配 -
并不需要进行转义,直接 [-]
也是可以的。
let str = '123456-8';
let reg = /[6-8-]/g;
console.log(str.match(reg)) // '6', '-', '8'
2.3 取非匹配 [^]
取非匹配就是取反的意思。
[a-z]
是匹配 a 到 z 的字符串,呢么 [^a-z]
就是只要不是 a 到 z 的字符串都进行匹配。
let str = 'abc,cccc,c12c, e9cc'
let reg = /[a-z][^1-9][a-z]c/g;
console.log(str.match(reg)); // c,cc
首先,第一位是 a 到 z 之间, c 符合。
第二位是非数值,这个 ,
英文逗号,也符合
第三位和第四位也符合。
2.4 使用 ^ 的注意事项
^
必须写在 []
里面,并且是第一位,才有取非的效果。
像/[a-z^1-9]c/g
这样是没有区分效果的,就只是个单纯的 ^
字符
let str = 'abc,cccc,c12c, e9cc, 91cc, Ac, ^c-c'
let reg = /[a-z^1-9]c/g;
console.log(str.match(reg));
// ['bc', 'cc', 'cc', '2c', '9c', '1c', '^c']
2.5 | 的使用
如果现在有一个需求,要匹配到代码中的 var
和 function
。如果使用 []
似乎有些不太合适,因为 [varfunction]
只是匹配 varfunction
中的任意一个而已,这个时候就要使用 |
,它有或的意思。
let str = `
var a = 5;
var b = 6;
function add(a, b){
return a + b;
}
`
let reg = /var|function/g;
console.log(str.match(reg)); // var var function
如果要有或这个功能,那么|
就不能写在 []
里面。
3. 元字符
元字符就是一些比较特殊的字符,比如可以匹配换行符,空白符等
元字符 | 描述 |
---|---|
. | 查找单个字符,除了换行和行结束符。 |
\w | 查找数字、字母及下划线。 |
\W | 查找非单词字符。 |
\d | 查找数字。 |
\D | 查找非数字字符。 |
\s | 查找空白字符。 |
\S | 查找非空白字符。 |
\b | 匹配单词边界。 |
\B | 匹配非单词边界。 |
\0 | 查找 NULL 字符。 |
\n | 查找换行符。 |
\f | 查找换页符。 |
\r | 查找回车符。 |
\t | 查找制表符。 |
\v | 查找垂直制表符。 |
\xxx | 查找以八进制数 xxx 规定的字符。 |
\xdd | 查找以十六进制数 dd 规定的字符。 |
\uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。 |
表格中的 \d
等价于 [0-9]
, \D
等价于 [^0-9]
。
// 匹配空白字符
let str = `
if(false){
console.log('111')
}
`
let reg = /\t./g;
console.log(str.match(reg)); // ['\ti', '\t\t', '\t}']
4. 重复匹配
在之前都是只匹配一个字符,那么接下来就试试匹配多个字符吧。
4.1 匹配一个或多个字符
+
加号就表示前面的表达式一个或多个字符,并且必须放在[]
外面。
let str = `
var a = 5;
var b = 6;
function add(a, b){
return a + b;
}
`
let reg = /var[\s]+[a-zA-Z0-9_$]+[\s]+=[\s]+[a-z-A-Z0-9_$]/g;
console.log(str.match(reg));
// ['var a = 5', 'var \t\tb = 6']
[\s]+
这个就是匹配多个空白字符的意思。
4.2 匹配零个或多个字符 *
let str = `
var a = 5;
var b = 6;
var c=8;
function add(a, b){
return a + b;
}
`
let reg = /var[\s]+[a-z-A-Z0-9_$]+[\s]*=[\s]*[a-z-A-Z0-9_$]/g;
console.log(str.match(reg));
// ['var a = 5', 'var \t\tb = 6', 'var c=8']
4.3 匹配零个或一个字符 ?
let str = `
var a = 5;
var b = 6;
var c=8;
function add(a, b){
return a + b;
}
`
let reg = /var[\s]+[a-z-A-Z0-9_$]+[\s]?=[\s]?[a-z-A-Z0-9_$]/g;
console.log(str.match(reg)); // ['var a = 5', 'var c=8']
4.4 设定重复匹配的次数 {n,m}
{n}, n 为大于 0 的数值
let str = `abbc, abbbc, abbbd, abbbbd`
let reg = /a[b]{3}[a-z]/g;
console.log(str.match(reg)); // ['abbbc', 'abbbd', 'abbbb']
{n, m} n,m 均为大于 0 的数值,n 到 m 的次数
let str = `abbc, abbbc, abbbd, abbbbd`
let reg = /a[b]{1,3}[a-z]/g;
console.log(str.match(reg)); //['abbc', 'abbbc', 'abbbd', 'abbbb']
{n, } 至少重复 n 次
{,n} 最多重复 n 次
4.5 贪婪型和懒惰型
贪婪型会在文段中,从头匹配到尾部,而不是适可而止
let str = `
This offer is not available to customers living in
AK and HI
`
let reg = /<[Bb]>.*<\/[Bb]>/g;
console.log(str.match(reg)); // AK and HI
AK and HI
这与预期不符合,不是两对 b 标签,因为第一个和最后一个
之间的元素都被
.*
匹配了。
这时需要懒惰型的运算符 *?
let str = `
This offer is not available to customers living in
AK and HI
`
let reg = /<[Bb]>.*?<\/[Bb]>/g;
console.log(str.match(reg)); // ['AK', 'HI']
贪婪型运算符 *, +, {n,}
懒惰型运算符 *?, +?, {n,}?
5. 位置匹配
5.1 \b 的使用
\b
也叫边界,单词的边界。
let str = `
var a = 5;
var b = 6;
function add(a, b){
return a + b;
}
`
let reg = /\bvar\b/g;
console.log(str.match(reg)); // var var
通常在 \b
是以空白符为边界的,在英文句子中匹配词语就会使用到。
5.2 匹配开头和结尾
^
开头,$
结尾,这两个是写在[]外面才有这个效果的,没错,^
写在 []
里面就是取反,写在外面就是匹配开头。
^
和 $
是字符串的边界。
let str = `a13456789, b123456789`
let reg = /^a[\w]+/g;
console.log(str.match(reg)); // ['a13456789']
let str = `a13456789b, b123456789c`
// let reg = /[\w]+b$/g; // null 因为该字符串的尾部边界是c
let reg = /[\w]+c$/g;
console.log(str.match(reg)); // b123456789c
5.3 分行匹配
在开头写上(?m)
,作用就是让 ^ 和 $ 能够匹配空白符,将空白符视为一个字符串分隔符。但是在 javascript 中分行模式叫做多行模式,而且也不是在开头加上 (?m)
,而是在末尾加上 m
。
let str = `
// 数值
let a = 1;
// 字符串
let b = '11'
// 注释
`;
let reg = /^\s*\/.*/mg;
console.log(str.match(reg))
6. 子表达式
在 ()
里面的就是正则表达式的子表达式。
现在我们要匹配一个 IP 地址:12.159.46.200
在不使用子表达式的情况下
let str = `12.159.46.200`
let reg = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{3}/g;
console.log(str.match(reg)); // ['12.159.46.200']
在一整个正则表达式中 \d{1,3}.
被重复写了三次,因此可以将其作为子表达式,在重复匹配三次,这样整个正则表达式就更加的简洁。
let str = `12.159.46.200`
let reg = /(\d{1,3}\.){3}\d{3}/g;
console.log(str.match(reg)); // ['12.159.46.200']
子表达式允许嵌套,解读是由内而外,但可读性极差,这里不进行举例
7. 回溯引用
回溯引用是引用正则中的子表达式,相当于变量
7.1 使用回溯引用匹配
let str = `
标题1
this is title 1
标题2
this is title 2
标题3
this is title 3
`
let reg = /<[Hh]([1-6])>.*?<\/[Hh]\1>/g;
console.log(str.match(reg));
// ['标题1
', '标题2
', '标题3
']
\1
表示第一个字表达式,\2
就是第二个表达式,如此类推,没有个数限制,另外\0
就是指整个表达式
7.2 使用回溯引用进行替换操作
通过下面代码对 var 关键字进行高亮标注
js函数
var a = 5