什么是正则表达式?
◼ 我们先来看一下维基百科对正则表达式的解释:
正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念;
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。
许多程序设计语言都支持利用正则表达式进行字符串操作。
◼ 简单概况:正则表达式是一种字符串匹配利器,可以帮助我们搜索、获取、替代字符串;
◼ 在JavaScript中,正则表达式使用RegExp类来创建,也有对应的字面量的方式:
正则表达式主要由两部分组成:模式(patterns)和修饰符(flags)
const res1 = new RegExp("hello","i")
const res2 = /hello/i
正则表达式的使用方法
◼有了正则表达式我们要如何使用它呢?
JavaScript中的正则表达式被用于RegExp的exec 和test 方法;
也包括String 的match、matchAll、replace、search 和split 方法;
方法 描述
实例方法:
exec 一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)。
test 一个在字符串中测试是否匹配的RegExp 方法,它返回true 或false。
字符串方法:
match 一个在字符串中执行查找匹配的String 方法,它返回一个数组,在未匹配到时会返回null。
matchAll 一个在字符串中执行查找所有匹配的String 方法,它返回一个迭代器(iterator)
search 一个在字符串中测试匹配的String 方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace 一个在字符串中执行查找匹配的String 方法,并且使用替换字符串替换掉匹配到的子字符串。
split 一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。
正则表达式的演练
// const re1 = new RegExp("abc","ig")
const re2 = /abc/ig
const message = "fdabc123 faBc31231 dfABC1312 AaaBc1231"
//使用正则表达式
//1.使用正则对象上的实例方法
//2.传入一个正则表达式
// 需求将所有的abc(忽略大小写)换成cba
//普通方式
const newmessage = message.replace("abc","cba")
console.log(newmessage) // fdcba123 faBc31231 dfABC1312 AaaBc1231
//使用正则表达式
const newmessage1 = message.replace(/abc/ig,"cba")
console.log(newmessage1) //fdcba123 fcba31231 dfcba1312 Aacba1231
// 需求:将字符串所有的数字删除
const newmessage2 = message.replace(/[0-9]+/ig,"")
console.log(newmessage2)//fdabc faBc dfABC AaaBc
正则表达式的方法演练
<!-- // 案例:让用户输入的账号必须包含5个a -->
输入账号:<input type="text" placeholder="请输入5个a">
<p class="tip"></p>
//1.使用正则表达式对象的实例方法
//webpack ->loader ->test:匹配文件名
// 1.1 test方法:检测字符串是否符合正则的规则->返回布尔类型
// 规则
const re1 = /^a{5}$/ig//^表示开头是a $表示结尾是c 中间必须是b
const iptEl = document.querySelector("input")
const tipEl = document.querySelector(".tip")
iptEl.onblur =function(){
const message = iptEl.value
if(re1.test(message)){
tipEl.textContent = "符合5个a规则"
tipEl.style = `color:green`
}
else{
tipEl.textContent = "不符合5个a规则"
tipEl.style = `color:red`
}
}
//1.2 exec方法:使用正则执行一个字符串-->第一个匹配的结果返回
const message1 = "fdabc123 faBc31231 dfABC1312 AaaBc1231"
const re2 = /abc/ig
const res2 = re2.exec(message1)
console.log(res2)//['abc', index: 2, input: 'fdabc123 faBc31231 dfABC1312 AaaBc1231', groups: undefined]
//1.3 match方法:字符串方法:匹配到所有的结果
const res3 = message1.match(re2)
console.log(res3)//(4) ['abc', 'aBc', 'ABC', 'aBc']
//1.4 matchAll 方法:返回一个迭代器,必须要全局
const res4 = message1.matchAll(re2)
console.log(res4)//RegExpStringIterator {}
console.log(res4.next())//{value: Array(1), done: false}
console.log(res4.next())//{value: Array(1), done: false}
console.log(res4.next())//{value: Array(1), done: false}
console.log(res4.next())//{value: Array(1), done: false}
console.log(res4.next())//{value: undefined, done: true}
//1.5 replace/replacAll 字符串方法:对字符串做替换
const res5 = message1.replace(re2,"cba")
console.log(res5)//fdcba123 fcba31231 dfcba1312 Aacba1231
const res6 = message1.replaceAll(re2,"bbb")
console.log(res6)//fdbbb123 fbbb31231 dfbbb1312 Aabbb1231
//1.6 split :切割
const res7 = message1.split(re2)
console.log(res7)//(5) ['fd', '123 f', '31231 df', '1312 Aa', '1231']
//1.7 search 方法:搜索结果
const res8 = message1.search(re2)
console.log(res8)// 2
修饰符flag的使用
◼ 常见的修饰符:
flag 含义
g 全部的,给我匹配全部的(global)
i 忽略大小写(ignore)
m 多行匹配(multiple)
◼ 需求:
获取一个字符串中所有的abc;
let message = "Hello ABC abc Abc ABc abC AAaBC"
const re1 = /abc/ig
const res1 = message.match(re1)
console.log(res1)
将一个字符串中的所有abc换成大写;
const res2 = message.replace(re1,"ABC")
console.log(res2)
规则–字符类(Character classes)
◼ 字符类(Character classes)是一个特殊的符号,匹配特定集中的任何符号。
字符 含义
\d(“d” 来自 “digit”) 数字:从0 到9 的字符。(一个\d只匹配一个)
\s(“s” 来自 “space”) 空格符号:包括空格,制表符\t,换行符\n和其他少数稀有字符,例如\v,\f和\r。
\w(“w” 来自“word”) “单字”字符:拉丁字母或数字或下划线_。
.(点) 点. 是一种特殊字符类,它与“除换行符之外的任何字符”匹配
◼ 反向类(Inverse classes)
\D 非数字:除\d 以外的任何字符,例如字母。
\S 非空格符号:除\s 以外的任何字符,例如字母。
\W 非单字字符:除\w 以外的任何字符, 例如非拉丁字母或空格。
//\d 匹配所有的数字
const message = "fdaaa4 2222411d66da6d8da8a8d"
const re1 = /\d+/ig
const re2 = /\d/ig
const rquest1 = message.match(re1)
console.log(rquest1)//(7) ['4', '2222411', '66', '6', '8', '8', '8']
console.log(message.match(re2))//(14) ['4', '2', '2', '2', '2', '4', '1', '1', '6', '6', '6', '8', '8', '8']
//反向类
const re3 = /\D/ig
console.log(message.match(re3))//(14) ['f', 'd', 'a', 'a', 'a', ' ', 'd', 'd', 'a', 'd', 'd', 'a', 'a', 'd']
规则–锚点(Anchors)
◼ 符号^ 和符号$ 在正则表达式中具有特殊的意义,它们被称为“锚点”。
符号^ 匹配文本开头;
符号$ 匹配文本末尾;
案例:
const message = "aaa my name is hdc."
// 字符串方法
if (message.startsWith("my")){
console.log("以my开头")
}
if(message.endsWith("hdc")){
console.log("以hdc结尾")
}
// 用正则:锚点
if(/^my/ig.test(message)){
console.log("以my开头")
}
// 匹配.本身的时候要用转意\.不然.表示任意字符
if(/hdc\.$/ig.test(message)){
console.log("以hdc.结尾")
}
const re = /^webKing$/
const re1 = /^web\w*g$/
const info = "webddddddg"
console.log(re.test(info))//false
console.log(re1.test(info))//true
◼ 词边界(Word boundary)
词边界\b 是一种检查,就像^ 和$ 一样,它会检查字符串中的位置是否是词边界。
词边界测试\b 检查位置的一侧是否匹配\w,而另一侧则不匹配“\w”
◼ 在字符串Hello, Java! 中,以下位置对应于 \b:
◼ 匹配下面字符串中的时间:
const message = "my aaanamebbb is hdc"
// 需求:name.name必须是一个单独的词
if(/name/ig.test(message)){
console.log("有name")//有name
}
// 词边界
if(/\bname\b/ig.test(message)){
console.log("有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))//(2) ['11:56', '12:00']
规则–转义字符串
◼ 如果要把特殊字符作为常规字符来使用,需要对其进行转义:
只需要在它前面加个反斜杠;
◼ 常见的需要转义的字符:
[] \ ^ $ . | ? * + ( )
斜杠符号‘/’ 并不是一个特殊符号,但是在字面量正则表达式中也需要转义;
◼ 练习:匹配所有以.js或者jsx结尾的文件名
◼ 在webpack当中,匹配文件名时就是以这样的方式。
//定义正则规则
const re = /./ig
const message = "abc.hdc"
const request = message.match(re)
console.log(request)//(7) ['a', 'b', 'c', '.', 'h', 'd', 'c']
const re1 = /\./ig
const request1 = message.match(re1)
console.log(request1)//['.']
// 特殊:/
// 转义
const re2 = /\//
// 练习:匹配所有以.js或者.jsx结尾的文件
const arrs= ["abc.html","bbc.css","hhh.js","eee.JS","rrr.jS","iii.jsx","ooo.Jsx"]
const re3 = /\.jsx?$/ig
const request2 = arrs.filter((arr)=>{return re3.test(arr)})
console.log(request2)
集合(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_] 相同;
◼ 案例:匹配手机号码
◼ 排除范围:除了普通的范围匹配,还有类似[^…] 的“排除”范围匹配;
// 手机号规则:以1开头 1[3-9]
//集合
const phoneStarts = ["123","134","332","112","151","333","141"]
const phoneStartsRe = /^1[3-9]\d/ig
const filterPhone = phoneStarts.filter( phoneStart=>{return phoneStartsRe.test(phoneStart)})
console.log(filterPhone)//(3) ['134', '151', '141']
// 手机号匹配
const phoneRe = /^1[3-9]\d{9}$/ig
const phoneNumber = ["!312321321","151531531","15478890876"]
const filterPhone1 = phoneNumber.filter(phone=>{return phoneRe.test(phone)})
console.log(filterPhone1)//['15478890876']
// 排除范围
//\d ->[0-9]
//\D =>[^0-9]
贪婪(Greedy)和惰性(lazy)模式
◼ 如果我们有这样一个需求:匹配下面字符串中所有使用《》包裹的内容
◼ 默认情况下的匹配规则是查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容
这种匹配的方式,我们称之为贪婪模式(Greedy)
◼ 懒惰模式中的量词与贪婪模式中的是相反的。
只要获取到对应的内容后,就不再继续向后匹配;
我们可以在量词后面再加一个问号‘?’ 来启用它;
所以匹配模式变为*? 或+?,甚至将'?' 变为??
/ 贪婪模式/惰性模式
const message = "我最喜欢的两本书:《黄金时代》和《沉默的大多数》、《我是白小飞》"
const nameRe = /《.+》/ig
const request1 = message.match(nameRe)
console.log(request1)// 贪婪模式的结果:《黄金时代》和《沉默的大多数》、《我是白小飞》
// 使用惰性模式
const nameRe1 = /《.+?》/ig
const request2 = message.match(nameRe1)
console.log(request2)//(3) ['《黄金时代》', '《沉默的大多数》', '《我是白小飞》']
捕获组(capturing group)
◼ 模式的一部分可以用括号括起来(...),这称为“捕获组(capturing group)”。
◼ 这有两个作用:
它允许将匹配的一部分作为结果数组中的单独项;
它将括号视为一个整体;
◼ 方法str.match(regexp),如果regexp 没有 g 标志,将查找第一个匹配并将它作为一个数组返回:
在索引0 处:完全匹配。
在索引1 处:第一个括号的内容。
在索引2 处:第二个括号的内容。
…等等…
◼ 案例:匹配到HTML标签,并且获取其中的内容
// 捕获组
const nameRe2 = /(《)(.+?)(》)/ig
const request3 = message.matchAll(nameRe2)
// console.log(request3)是一个迭代器
for (const item of request3){
console.log(item[2])
}
// 将一个捕获组作为整体
const info = "asdsadsabcabcadsabcadasdaabcsdassabcabcadsad"
//需求:捕获连续两个abc abcabc
const infoRe = /(abc){2}/ig
const infoRequest = info.match(infoRe)
console.log(infoRequest)//(2) ['abcabc', 'abcabc']
案例:
const str = "<div>title</div>"
const strRe = /<(.+?)>/
const request5 = str.match(strRe)
console.log(request5[1])
捕获组的补充
◼ 命名组:
用数字记录组很困难。
对于更复杂的模式,计算括号很不方便。我们有一个更好的选择:给括号起个名字。
这是通过在开始括号之后立即放置?<name> 来完成的。
const result = message.match(/(?<hdc>hel)ol/i)
◼ 非捕获组:
有时我们需要括号才能正确应用量词,但我们不希望它们的内容出现在结果中。
可以通过在开头添加?: 来排除组。
const result = message.match(/(?<hdc>hel)(?:ol){2}/i)
◼ or是正则表达式中的一个术语,实际上是一个简单的“或”。
在正则表达式中,它用竖线| 表示;
通常会和捕获组一起来使用,在其中表示多个值;
const info = "asdsadsabcabccbaadsabcadasdanbaabcsdassabcabcadsad"
//需求:捕获连续两个abc abcabc
const infoRe = /(abc|cba|nba){2}/ig
const infoRequest = info.match(infoRe)
console.log(infoRequest)//(3) ['abcabc', 'nbaabc', 'abcabc']