正则表达式
# 什么是正则表达式
正则表达式是一种字符串匹配利器,可以搜索,获取,替换字符串
在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式;正则表达式主要由两部分组成:模式(patterns)和修饰(flags)
const r1 = new RegExp("hello","i")
const r2 = /hello/i
const reg1 = new RegExp("abx", "i") // 参数一:匹配的规则,参数二:修饰符
const reg2 = /aaaa/
2
3
4
5
# 正则表达式的使用方法
JavaScript中的正则表达式被用于RegExp的exec
和test
方法,也包括String的match
,matchAll
,replace
,search
和split
方法
方法 | 描述 |
---|---|
exec | 在字符串中执行查找匹配的 RegExp 方法,返回一个数组(未匹配到则返回 null) |
test | 在字符串中测试是否匹配的 RegExp 方法,它返回true 或false |
match | 在字符串中执行查找匹配的 String 方法,它返回一个数组,在未匹配到时会返回 null |
matchAll | 在字符串中执行查找所有匹配的 String 方法,它返回一个迭代器 |
search | 在字符串中测试匹配的 String 方法,它返回匹配到的位置索引,或者在失败时返回-1 |
replace | 在字符串中执行查找匹配的 String 方法,并且使用替换字符串替换掉匹配到的子字符串 |
split | 使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String 方法。 |
const str = "ABCabcAbc";
const reg = RegExp("AbC");
console.log(reg.exec(str));
console.log(reg.test(str));
console.log(str.match("a"));
console.log(str.matchAll("A"));
console.log(str.search("B"));
2
3
4
5
6
7
# 修饰符flag的使用
常见的修饰符
flag | 含义 |
---|---|
g | 全部匹配 |
i | 忽略大小写 |
m | 多行匹配 |
案例:
- 获取一个字符串中所有的abc
- 将一个字符串中的所有abc换成大写
let message = "Hello ABA, abc, Abc, AAaBC";
const pattern = /abc/gi;
const results = message.match(pattern);
console.log(results);
message = message.replaceAll(pattern, "ABC");
console.log(message);
2
3
4
5
6
# 字符类规则
字符类是一个特殊的符号,匹配特定集中的任何符号
字符 | 含义 |
---|---|
\d (digit) | 从0到9 的字符 |
\s (space) | 空格符号:包括空格,制表符\t,换行符\n和其它少数稀有字符例如\v,\f\r等 |
\w (word) | 单字字符,拉丁字母或者数字或下划线 相当于 [a-zA-Z0-9] |
. | 点,是一种特殊字符类,它与除换行符之外的任何字符匹配 |
反向类
- \D: 非数字,除\d以外的任何字符,例如字母
- \S: 非空格符号,除\s以外的任何字符,例如字母
- \W: 非单字字符,除\w以外的任何字符,例如非拉丁字母或者空格
// 匹配字符串的数字
const message = "asbd1234 asj879"
// 匹配一个或多个数字
const re = /\d+/ig
console.log(message.match(re));
const messaget = "CSS2.5"
const pattern = /CSS\d(\.\d)?/i
console.log(messaget.match(pattern))
2
3
4
5
6
7
8
# 锚点规则
符号^
和符号$
在正则表达式中具有特殊的意义,被称为"锚点"
- 符号
^
匹配文本开头 - 符号
$
匹配文本末尾
const message = "My name is zs.";
// 字符串的匹配方法
if (message.startsWith("my")) {
console.log("以my开头");
}
if (message.endsWith("zs")) {
console.log("以zs结尾");
}
// 正则
if (/^my/i.test(message)) {
console.log("以my开头");
}
// 这里的 . 匹配需要使用转义 \.
if (/zs\.$/i.test(message)) {
console.log("以zs结尾");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
词边界
- 词边界
\b
是一种检查,就像^
和$
一样,它会检查字符串中的位置是否是词边界 - 词边界测试
\b
检查位置的一侧是否是\w
,而另一侧则不匹配\w
- 字符串测试一边应该只有空格
const message = "now time 22:45, number is 123.456";
const timepattern = /\b\d\d:\d\d\b/g;
console.log(message.match(timepattern));
// 需要匹配 name, name必须是一个单独的词
const message = "My name is zs.";
if (/\bname\b/i.test(message)) {
console.log("name有边界");
}
2
3
4
5
6
7
8
9
# 转义字符规则
如果需要使用特殊字符作为常规字符来使用,需要对其进行转义,只要在其前面添加\
即可
列举一些常见的的需要转义的字符
[] \ $ . | ? * + ( )
/
并不是一个特殊的符号,但是在字面量正则表达式中也需要转义。
// 匹配所有以.js或者jsx结尾的文件名
const fileNames = ["abc.js", "cba.java", "nba.html", "mba.js", "aaa.jsx"];
const newNames = fileNames.filter((item) => {
return /\.jsx?$/.test(item);
});
console.log(newNames);
2
3
4
5
6
# 集合和范围
有时候值需要选择多个匹配字符的其中之一就可以,在[]
中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”
集合
例如: [eao]意味着查找在3个字符 a,e,或者o中的任意一个
范围
- [] 也可以包含字符范围
- 例如:[a-z]会匹配从a到z范围内的字母,[0-5]表示从0到5的数字
- [0-9A-F]表示两个范围:搜索一个字符,满足数字0到9或者字母A到F
// 例如:需要匹配手机号码
const phoneStarts = ["132", "130", "110", "120", "133", "155"];
const phoneStartRe = /^1[356789]\d/;
const filterPhone = phoneStarts.filter((phone) =>
phoneStartRe.test(phone)
);
console.log(filterPhone);
const phoneNum = "13123456789";
const phoneRe = /^1[3-9]\d{9}$/;
console.log(phoneRe.test(phoneNum));
2
3
4
5
6
7
8
9
10
11
排除范围
除了普通的范围匹配,还有类似
[^...]
的排除范围匹配例如
\d
可以表示[0-9]
\D
可以表示为[^0-9]
# 量词
假如有一个字符串,需要找出这个字符串中所包含的所有数字;由于数量是不同的,因此需要给出数量一个范围;这种用来形容所需要的数量的词被称为量词
const str = "+7(903)-123-45-67";
const strRe = /\d+/gi;
console.log(str.match(strRe));
2
3
数量 {n}
- 例如确切的位数:{5}
- 某个范围的位数:{3,5}
- 注意:数字之间不能有空格
缩写的含义
+
: 表示一个或者多个,相当于{1,}
?
: 表示零个或者一个,相当于{0,1}
,使符号变得可选*
: 表示领个或者多个,相当于{0,}
,也就是说这个字符可以多次出现或者不出现
// 匹配 3-5个a
const re = /a{3,5}/gi;
const message = "faaaabghaafjkaaaai";
const results = message.match(re);
console.log(results);
2
3
4
5
案例:匹配html的开始和结束标签
const htmlStr =
"<div><h1>我是标题</h1></span></span><span>我是内容</span></div>";
const htmlStrRe = /<\/*[a-z][a-z0-9]*>/gi;
console.log(htmlStr.match(htmlStrRe));
2
3
4
# 贪婪和惰性模式
需求:匹配字符串中所有使用 <<>> 包裹的内容
const message = "我最喜欢的两本书: <<黄金时代>> 和 <<沉默的大多数>> ";
const messageRe = /<<.+>>/gi;
console.log(message.match(messageRe)); // ['<<黄金时代>> 和 <<沉默的大多数>>']
2
3
默认情况下这种匹配规则查到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容,这种匹配方式称之为**“贪婪模式”**
懒惰模式中的量词与贪婪模式中是相反的
- 主要获取到对应的内容后,就不再继续向后匹配
- 可以在量词后面加一个
?
来启用 - 最终匹配模式由
.?
或者.+
变为.??
,.+?
,甚至将?
变为??
const message = "我最喜欢的两本书: <<黄金时代>> 和 <<沉默的大多数>> ";
const messageRe = /<<.+?>>/gi;
console.log(message.match(messageRe)); // ['<<黄金时代>>', '<<沉默的大多数>>']
2
3
类似于.+
的这种匹配模式称为贪婪模式,.+?
称为惰性模式
# 捕获组
模式的一部分可以用括号括起来(...)
这称为”捕获组“
作用
- 允许将匹配的一部分作为结果数组中的单独项
- 将括号视为一个整体
// 分组
const message =
"我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /( <<)(.+?)(>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
console.log(item);
}
2
3
4
5
6
7
8
// 将捕获组作为整体
// 匹配abc一起总共至少出现两次,将abc作为一个整体
const info = "dfabcabcksdal;fkas;ldjabcabcabc";
const abcRe = /(abc){2,}/gi;
console.log(info.match(abcRe)); // ['abcabc', 'abcabcabc']
2
3
4
5
# 捕获组补充
命名组
- 使用数字记录组比较困难
- 对于更复杂的模式,计算括号不方便,因此提供一个更好的选择:起名字
- 形式:在开始括号之后立即防止
?<name>
实现
const message =
"我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /( <<)(?<bookname>.+?)(>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
console.log(item);
}
2
3
4
5
6
7
非捕获组
- 有时需要括号才能正确应用量词,但是不希望内容出现结果中
- 可以通过在开头添加
?:
来排除组
const message =
"我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /(?: <<)(?<bookname>.+?)(?:>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
console.log(item);
}
2
3
4
5
6
7
or是正则表达式中的一个术语,实际上是一个简单的或
- 在正则表达式中,使用竖线
|
表示 - 通常和捕获组一起来使用,在其中表示多个值
const info = "abc123343abcabcuiohjklajslk123ajklsdfjcbacba";
const abcRe = /(abc|cba|nba){2,}/gi;
console.log(info.match(abcRe)); // ['abcabc', 'cbacba']
2
3
# 案例-正则表达式解析歌词
const lyricString =
"[00:00.000] 作词 : 许嵩\n[00:01.000] 作曲 : 许嵩\n[00:02.000] 编曲 : 许嵩\n[00:22.240]天空好想下雨\n[00:24.380]我好想住你隔壁\n[00:26.810]傻站在你家楼下\n[00:29.500]抬起头数乌云\n[00:31.160]如果场景里出现一架钢琴\n[00:33.640]我会唱歌给你听\n[00:35.900]哪怕好多盆水往下淋\n[00:41.060]夏天快要过去\n[00:43.
340]请你少买冰淇淋\n[00:45.680]天凉就别穿短裙\n[00:47.830]别再那么淘气\n[00:50.060]如果有时不那么开心\n[00:52.470]我愿意将格洛米借给你\n[00:55.020]你其实明白我心意\n[00:58.290]为你唱这首歌没有什么风格\n[01:02.976]它仅仅代表着我想给你快乐\n[01:07.840]为你解冻冰河为你做一只扑火的飞蛾\n[01:12.998]没有什么事情是不值
得\n[01:17.489]为你唱这首歌没有什么风格\n[01:21.998]它仅仅代表着我希望你快乐\n[01:26.688]为你辗转反侧为你放弃世界有何不可\n[01:32.328]夏末秋凉里带一点温热有换季的颜色\n[01:41.040]\n[01:57.908]天空好想下雨\n[01:59.378]我好想住你隔壁\n[02:02.296]傻站在你家楼下\n[02:03.846]抬起头数乌云\n[02:06.183]如果场景里出现一
架钢琴\n[02:08.875]我会唱歌给你听\n[02:10.974]哪怕好多盆水往下淋\n[02:15.325]夏天快要过去\n[02:18.345]请你少买冰淇淋\n[02:21.484]天凉就别穿短裙\n[02:22.914]别再那么淘气\n[02:25.185]如果有时不那么开心\n[02:27.625]我愿意将格洛米借给你\n[02:30.015]你其实明白我心意\n[02:33.327]为你唱这首歌没有什么风格\n[02:37.976]
它仅仅代表着我想给你快乐\n[02:42.835]为你解冻冰河为你做一只扑火的飞蛾\n[02:48.406]没有什么事情是不值得\n[02:52.416]为你唱这首歌没有什么风格\n[02:57.077]它仅仅代表着我希望你快乐\n[03:01.993]为你辗转反侧为你放弃世界有何不可\n[03:07.494]夏末秋凉里带一点温热\n[03:11.536]\n[03:20.924]为你解冻冰河为你做一只扑火的飞蛾\n
[03:26.615]没有什么事情是不值得\n[03:30.525]为你唱这首歌没有什么风格\n[03:35.196]它仅仅代表着我希望你快乐\n[03:39.946]为你辗转反侧为你放弃世界有何不可\n[03:45.644]夏末秋凉里带一点温热有换季的颜色\n";
// 分隔称为数组
const lyricStringLines = lyricString.split("\n");
// 这里要获取的是时间的每一位,因此需要使用分组,通过分组获取到时间的每一位数据
const timeRe = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/i;
const result = [];
for (const lyricStr of lyricStringLines) {
if (!lyricStr) continue;
// 将所有的时间转换为毫秒
const timeInfo = lyricStr.match(timeRe);
const min = timeInfo[1] * 60 * 1000;
const sec = timeInfo[2] * 1000;
const minSec =
timeInfo[3].length === 3 ? timeInfo[3] * 1 : timeInfo[3] * 10;
const time = min + sec + minSec;
const lyric = lyricStr.replace(timeRe, "").trim();
result.push({
time: time,
content: lyric,
});
}
console.log(result);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 案例--时间格式化
假如从服务器中获取时间戳,需要转成时间格式
/**
* @param {timestamp} 时间戳
**/
function formatTime(timestamp, format) {
const date = new Date(timestamp);
// 将时间正则格式作为对应的key
const dateObj = {
"y+": date.getFullYear(),
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
};
for (const key in dateObj) {
if (new RegExp(key).test(format)) {
const value = (dateObj[key] + "").padStart(2, "0");
format = format.replace(new RegExp(key), value);
}
}
return format;
}
const timestamp = new Date().getTime();
console.log(formatTime(timestamp, "yyyy-MM-dd HH:mm:ss"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24