目录

正则表达式

# 什么是正则表达式

正则表达式是一种字符串匹配利器,可以搜索,获取,替换字符串

在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式;正则表达式主要由两部分组成:模式(patterns)和修饰(flags)

const r1 = new RegExp("hello","i")
const r2 = /hello/i

const reg1 = new RegExp("abx", "i") // 参数一:匹配的规则,参数二:修饰符
const reg2 = /aaaa/
1
2
3
4
5

# 正则表达式的使用方法

JavaScript中的正则表达式被用于RegExp的exectest方法,也包括String的matchmatchAllreplacesearchsplit方法

方法 描述
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"));
1
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);
1
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))
1
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结尾");
}
1
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有边界");
}
1
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);
1
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));
1
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));
1
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);
1
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));
1
2
3
4

# 贪婪和惰性模式

需求:匹配字符串中所有使用 <<>> 包裹的内容

const message = "我最喜欢的两本书: <<黄金时代>> 和 <<沉默的大多数>> ";
const messageRe = /<<.+>>/gi;
console.log(message.match(messageRe)); // ['<<黄金时代>> 和 <<沉默的大多数>>']
1
2
3

默认情况下这种匹配规则查到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容,这种匹配方式称之为**“贪婪模式”**

懒惰模式中的量词与贪婪模式中是相反的

  • 主要获取到对应的内容后,就不再继续向后匹配
  • 可以在量词后面加一个?来启用
  • 最终匹配模式由.?或者.+变为.??,.+?,甚至将?变为??
const message = "我最喜欢的两本书: <<黄金时代>> 和 <<沉默的大多数>> ";
const messageRe = /<<.+?>>/gi;
console.log(message.match(messageRe)); // ['<<黄金时代>>', '<<沉默的大多数>>']
1
2
3

类似于.+的这种匹配模式称为贪婪模式,.+?称为惰性模式

# 捕获组

模式的一部分可以用括号括起来(...)这称为”捕获组

作用

  • 允许将匹配的一部分作为结果数组中的单独项
  • 将括号视为一个整体
// 分组
const message =
  "我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /( <<)(.+?)(>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
  console.log(item);
}
1
2
3
4
5
6
7
8

image

// 将捕获组作为整体
// 匹配abc一起总共至少出现两次,将abc作为一个整体
const info = "dfabcabcksdal;fkas;ldjabcabcabc";
const abcRe = /(abc){2,}/gi;
console.log(info.match(abcRe)); // ['abcabc', 'abcabcabc']
1
2
3
4
5

# 捕获组补充

命名组

  • 使用数字记录组比较困难
  • 对于更复杂的模式,计算括号不方便,因此提供一个更好的选择:起名字
  • 形式:在开始括号之后立即防止?<name>实现
const message =
  "我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /( <<)(?<bookname>.+?)(>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
  console.log(item);
}
1
2
3
4
5
6
7

image

非捕获组

  • 有时需要括号才能正确应用量词,但是不希望内容出现结果中
  • 可以通过在开头添加?:来排除组
const message =
  "我最喜欢的三本书: <<黄金时代>> 和 <<沉默的大多数>> 、 <<一只特立独行的猪>> ";
const nameRe = /(?: <<)(?<bookname>.+?)(?:>> )/gi;
const iterator = message.matchAll(nameRe);
for (const item of iterator) {
  console.log(item);
}
1
2
3
4
5
6
7

image

or是正则表达式中的一个术语,实际上是一个简单的或

  • 在正则表达式中,使用竖线|表示
  • 通常和捕获组一起来使用,在其中表示多个值
const info = "abc123343abcabcuiohjklajslk123ajklsdfjcbacba";
const abcRe = /(abc|cba|nba){2,}/gi;
console.log(info.match(abcRe)); //  ['abcabc', 'cbacba']
1
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);
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
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"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
上次更新: 2023/02/06, 13:33:08
最近更新
01
防抖和节流
02-06
02
async_await函数
12-30
03
迭代器生成器
12-16
更多文章>