正则表达式
什么是正则表达式?
我们先来看一下维基百科对正则表达式的解释:
- 正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常 规表示法,是计算机科学的一个概念;
- 正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
- 许多程序设计语言都支持利用正则表达式进行字符串操作。
简单概况:正则表达式是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串;
在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式:
正则表达式主要由两部分组成:模式(patterns)和修饰符(flags)
// 创建正则
// 1> 匹配的规则pattern
// 2> 匹配的修饰符flags
const re1 = new RegExp("abc", "ig")
const re2 = /abc/ig // 我是注释
正则表达式的使用方法
- 有了正则表达式我们要如何使用它呢?
- JavaScript中的正则表达式被用于 RegExp 的 exec 和 test 方法;
- 也包括 String 的 match、matchAll、replace、search 和 split 方法;
方法 | 所属实例 | 描述 |
---|---|---|
exec | RegExp | 一个在字符串中执行查找匹配的 RegExp 方法,它返回一个数组(未匹配到则返回 null)。 |
test | RegExp | 一个在字符串中测试是否匹配的 RegExp 方法,它返回 true 或 false。 |
match | String | 一个在字符串中执行查找匹配的 String 方法,它返回一个数组,在未匹配到时会返回 null。 |
matchAll | String | 一个在字符串中执行查找所有匹配的 String 方法,它返回一个迭代器(iterator)。 |
search | String | 一个在字符串中测试匹配的 String 方法,它返回匹配到的位置索引,或者在失败时返回-1。 |
replace | String | 一个在字符串中执行查找匹配的 String 方法,并且使用替换字符串替换掉匹配到的子字符串。 |
split | String | 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中 的 String 方法。 |
// 创建正则
const re1 = /abc/ig
const message = "fdabc123 faBC323 dfABC222 A2324aaBc"
// 1.使用正则对象上的实例方法
// 1.1.exec方法: 使用正则执行一个字符串
const res1 = re1.exec(message)
console.log(res1)
// 1.2.test方法: 检测某一个字符串是否符合正则的规则
if (re1.test(message)) { //返回true或false
console.log("message符合规则")
} else {
console.log("message不符合规则")
}
// 2.使用字符串的方法, 传入一个正则表达式
// 2.1. match方法:
//加上g,只会返回匹配到的字符串
const result2 = message.match(re1)//['abc', 'aBC', 'ABC', 'aBc']
console.log(result2)
//不加g,会返回匹配到的详情数据
const result2_1 = message.match(/abc/i)//['abc', index: 2, input: 'fdabc123 faBC323 dfABC222 A2324aaBc', groups: undefined]
console.log(result2_1);
// 2.2. matchAll方法
//由于match加上g无法获取到匹配详情数据,所以ES8加上这个API
// 注意: matchAll传入的正则修饰符必须加g,返回迭代器,可以获取到详情数据。
const result3 = message.matchAll(re1)
for (const item of result3) {
console.log(item)
}
// 2.3. replace/replaceAll方法
const result7 = message.replace(re1,"123")
console.log(result7);
//replaceAll必须加上g
const result7_1 = message.replaceAll(re1,"123")
console.log(result7_1);
// 2.4. split方法
const result4 = message.split(re1)
console.log(result4)
// 2.5. search方法
const result5 = message.search(re1)
console.log(result5)
// 案例: 让用户输入的账号必须是aaaaa以上
const inputEl = document.querySelector("input")
const tipEl = document.querySelector(".tip")
inputEl.oninput = function() {
const value = this.value
if (/^a{5,8}$/.test(value)) {
tipEl.textContent = "输入的内容符合规则要求"
} else {
tipEl.textContent = "输入的内容不符合规则要求, 请重新输入"
}
}
修饰符flag的使用
- 常见的修饰符:
- g:全部的,给我匹配全部的
- i:忽略大小写
- m:多行匹配
- 需求:
- 获取一个字符串中所有的abc;
- 将一个字符串中的所有abc换成大写;
规则 – 字符类(Character classes)
字符类(Character classes) 是一个特殊的符号,匹配特定集中的任何符号。
\d
(“d” 来自 “digit”)- 数字:从 0 到 9 的字符。
\s
(“s” 来自 “space”)- 空格符号:包括空格,制表符 \t,换行符 \n 和其他少数稀有字符,例如 \v,\f 和 \r。
\w
(“w” 来自 “word”)- “单字”字符:拉丁字母或数字或下划线 _。
.
(点)- 点 . 是一种特殊字符类,它与 “除换行符之外的任何字符” 匹配
- 反向类(Inverse classes)
\D
非数字:除 \d 以外的任何字符,例如字母。\S
非空格符号:除 \s 以外的任何字符,例如字母。\W
非单字字符:除 \w 以外的任何字符,例如非拉丁字母或空格。
const message = "fdaa4 22242asfasdf2242"
const re = /\d+/ig
// \d -> 所有的数字 0~9
console.log(message.match(re))
规则 – 锚点(Anchors)
- 符号 ^ 和符号 $ 在正则表达式中具有特殊的意义,它们被称为“锚点”。
- 符号 ^ 匹配文本开头;
- 符号 $ 匹配文本末尾;
const message = "My name is WHY."
/* 字符串方法
if (message.startsWith("my")) {
console.log("以my开头")
}
if (message.endsWith("why")) {
console.log("以why结尾")
}*/
// 正则: 锚点
if (/^my/i.test(message)) {
console.log("以my开头")
}
if (/why\.$/i.test(message)) {
console.log("以why结尾")
}
const re = /^coder$/
const info = "codaaaer"
console.log(re.test(info)) // false
词边界(Word boundary)
- 词边界 \b 是一种检查,就像 ^ 和 $ 一样,它会检查字符串中的位置是否是词边界。
- 词边界测试 \b 检查位置的一侧是否匹配 \w,而另一侧则不匹配 “\w”
在字符串 Hello, Java! 中,以下位置对应于 \b:
- 匹配下面字符串中的时间:
// \w
const message = "My name! is WHY."
// 需求: name, name必须是一个单独的词
// 词边界
if (/\bname\b/i.test(message)) {
console.log("有name, name有边界")
}
// 词边界的应用
const infos = "now time is 11:56, 12:00 eat food, number is 123:456"
const timeRe = /\b\d\d:\d\d\b/ig
console.log(infos.match(timeRe))
规则 – 转义字符串
如果要把特殊字符作为常规字符来使用,需要对其进行转义:
- 只需要在它前面加个反斜杠;
常见的需要转义的字符:
[] \ ^ $ . | ? \* + ( )
- 斜杠符号 ‘/’ 并不是一个特殊符号,但是在字面量正则表达式中也需要转义;
// 定义正则: 对.转义
const re = /\./ig
const message = "abc.why"
const results = message.match(re)
console.log(results)
- 练习:匹配所有以.js或者jsx结尾的文件名
const fileNames = ["abc.html", "Home.jsx", "index.html", "index.js", "util.js", "format.js"]
// 获取所有的js的文件名(webpack)
// x?: x是0个或者1个
const jsfileRe = /\.jsx?$/
const newFileNames = fileNames.filter(filename => jsfileRe.test(filename))
console.log(newFileNames)
- 在webpack当中,匹配文件名时就是以这样的方式。
集合(Sets)和范围(Ranges)
有时候我们只要选择多个匹配字符的其中之一就可以:
- 在方括号
[…]
中的几个字符或者字符类意味着“搜索给定的字符中的任意一个”;
- 在方括号
集合(Sets)
- 比如说,[eao] 意味着查找在 3 个字符 ‘a’、‘e’ 或者 `‘o’ 中的任意一个;
范围(Ranges)
- 方括号也可以包含字符范围;
- 比如说
- [a-z] 会匹配从 a 到 z 范围内的字母,[0-5] 表示从 0 到 5 的数字;
- [0-9A-F] 表示两个范围:它搜索一个字符,满足数字 0 到 9 或字母 A 到 F;
- \d —— 和 [0-9] 相同;
- \w —— 和 [a-zA-Z0-9_] 相同;
案例:匹配手机号码
const phoneNum = "133888855555"
const phoneRe = /^1[3-9]\d{9}$/
console.log(phoneRe.test(phoneNum))
- 排除范围:除了普通的范围匹配,还有类似
[^…]
的“排除”范围匹配;
// \d -> [0-9]
// \D -> [^0-9]
量词(Quantifiers)
假设我们有一个字符串 +7(903)-123-45-67,并且想要找到它包含的所有数字。
- 因为它们的数量是不同的,所以我们需要给与数量一个范围;
- 用来形容我们所需要的数量的词被称为量词( Quantifiers )。
数量 {n}
- 确切的位数:{5}
- 某个范围的位数:{3,5}
- 确切位数或以上:{4,}
缩写:
+
:代表“一个或多个”,相当于 {1,}?
:代表“零个或一个”,相当于 {0,1}。换句话说,它使得符号变得可选;*
:代表着“零个或多个”,相当于 {0,}。也就是说,这个字符可以多次出现或不出现;
// 1.量词的使用
const re = /a{3,5}/ig
const message = "fdaaa2fdaaaaaasf42532fdaagjkljlaaaaf"
const results = message.match(re)
console.log(results)
// 2.常见的符号: +/?/*
// +: {1,}
// ?: {0,1}
// *: {0,}
// 3.案例: 字符串的html元素, 匹配出来里面所有的标签
const htmlElement = "<div><span>哈哈哈</span><h2>我是标题</h2></div>"
const tagRe = /<\/?[a-z][a-z0-9]*>/ig
const results2 = htmlElement.match(tagRe)
console.log(results2)
贪婪( Greedy)和惰性( lazy)模式
- 如果我们有这样一个需求:匹配下面字符串中所有使用《》包裹的内容
const message = "我最喜欢的两本书: 《黄金时代》和《沉默的大多数》、《一只特立独行的猪》"
const nameRe = /《.+》/ig
const result1 = message.match(nameRe)
console.log(result1)//《黄金时代》和《沉默的大多数》、《一只特立独行的猪》
默认情况下的匹配规则是查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容
- 这种匹配的方式,我们称之为贪婪模式(Greedy)
懒惰模式中的量词与贪婪模式中的是相反的。
- 只要获取到对应的内容后,就不再继续向后匹配;
- 我们可以在量词后面再加一个问号 ‘?’ 来启用它;
- 所以匹配模式变为
*?
或+?
,甚至将?
变为??
// 1.贪婪模式/惰性模式
const message = "我最喜欢的两本书: 《黄金时代》和《沉默的大多数》、《一只特立独行的猪》"
// 使用惰性模式
const nameRe = /《.+?》/ig
const result1 = message.match(nameRe)
console.log(result1) //['《黄金时代》', '《沉默的大多数》', '《一只特立独行的猪》']
捕获组(capturing group)
- 模式的一部分可以用括号括起来 (...),这称为“捕获组(capturing group)”。
- 这有两个作用:
- 它允许将匹配的一部分作为结果数组中的单独项;
- 它将括号视为一个整体;
// 1.它允许将匹配的一部分作为结果数组中的单独项
const message = "我最喜欢的两本书: 《黄金时代》和《沉默的大多数》、《一只特立独行的猪》"
const nameRe = /《(.+?)》/ig
const iterator = message.matchAll(nameRe)
//第一个括号,会出现在数组1的位置
for (const item of iterator) {
console.log(item) //['《黄金时代》', '黄金时代', ....]
}
// 2.将捕获组作为整体
const info = "dfabcabcfabcdfdabcabcabcljll;jk;j"
const abcRe = /(abc){2,}/ig
console.log(info.match(abcRe))
- 命名组:
- 用数字记录组很困难。
- 对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字。
- 这是通过在开始括号之后立即放置
?<name>
来完成的。
const message = "我最喜欢的两本书: 《黄金时代》和《沉默的大多数》、《一只特立独行的猪》"
const nameRe = /《(?<book>.+?)》/ig
const iterator = message.matchAll(nameRe)
//命名组会出现在match方法的返回的数组的groups属性中
for (const item of iterator) {
console.log(item) //[.....groups: {book: "黄金时代"}.......]
}
- 非捕获组:
- 有时我们需要括号才能正确应用量词,但我们不希望它们的内容出现在结果中。
- 可以通过在开头添加
?:
来排除组。
const message = "我最喜欢的两本书: 《黄金时代》和《沉默的大多数》、《一只特立独行的猪》"
const nameRe = /(?:《)(?<why>.+?)(?:》)/ig
const iterator = message.matchAll(nameRe)
for (const item of iterator) {
console.log(item) //《和》不会出现在捕获组里。
}
- or是正则表达式中的一个术语,实际上是一个简单的“或”。
- 在正则表达式中,它用竖线 | 表示;
- 通常会和捕获组一起来使用,在其中表示多个值;
- 和
[...]
的区别:[...]
是针对单个字符的,(...|...)
是针对多个字符的
// 1.将捕获组作为整体
const info = "dfabcabcfabcdfdabcabcabcljcbacballnbanba;jk;j"
const abcRe = /(abc|cba|nba){2,}/ig
console.log(info.match(abcRe))// ['abcabc', 'abcabcabc', 'cbacba', 'nbanba']
案例练习
案例练习 – 歌词解析
- **歌词解析:
/*
currentTime: 2000
[00:00.000] 作词 : 许嵩 -> { time: 0, content: "作词 : 许嵩" }
[00:01.000] 作曲 : 许嵩 -> { time: 1000, content: "作曲 : 许嵩" }
[00:02.000] 编曲 : 许嵩
[00:22.240]天空好想下雨
*/
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"
// 一. 没有封装
// 1.根据\n切割字符串
const lyricLineStrings = lyricString.split("\n")
// console.log(lyricLineStrings)
// 2.针对每一行歌词进行解析
// [01:22.550]夏末秋凉里带一点温热有换季的颜色
const timeRe = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/i
const lyricInfos = []
for (const lineString of lyricLineStrings) {
// 1.获取时间
const result = lineString.match(timeRe)
if (!result) continue
const minuteTime = result[1] * 60 * 1000
const secondTime = result[2] * 1000
const mSecondTime = result[3].length === 3? result[3]*1: result[3]*10
const time = minuteTime + secondTime + mSecondTime
// 2.获取内容
const content = lineString.replace(timeRe, "").trim()
// 3.将对象放到数组中
lyricInfos.push({ time, content })
}
console.log(lyricInfos)
案例练习 – 时间格式化
- 时间格式化:从服务器拿到时间戳,转成想要的时间格式
// timestamp: 1659252290626
// yyyy/MM/dd hh:mm:ss
// yyyy*MM*dd hh-mm-ss
// dayjs/moment
function formatTime(timestamp, fmtString) {
// 1.将时间戳转成Date
const date = new Date(timestamp)
// // 2.获取到值
// const year = date.getFullYear()
// const month = date.getMonth() + 1
// const day = date.getDate()
// const hour = date.getHours()
// const minute = date.getMinutes()
// const second = date.getSeconds()
// // 3.创建正则
// const yearRe = /y+/
// const monthRe = /M+/
// 2.正则和值匹配起来
const dateO = {
"y+": date.getFullYear(),
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds()
}
// 3.for循环进行替换
for (const key in dateO) {
const keyRe = new RegExp(key)
if (keyRe.test(fmtString)) {
const value = (dateO[key] + "").padStart(2, "0")
fmtString = fmtString.replace(keyRe, value)
}
}
return fmtString
}
// 某一个商品上架时间, 活动的结束时间
const timeEl = document.querySelector(".time")
const productJSON = {
name: "iPhone",
newPrice: 4999,
oldPrice: 5999,
endTime: 1659252301637
}
timeEl.textContent = formatTime(productJSON.endTime, "hh:mm:ss yyyy*MM*dd")
更多正则查询: [https://c.runoob.com/front-end/854/](https://c.runoob.com/front-end/854/)**