正则表达式
regular expression:RegExp
用来处理字符串的规则
- 只能处理字符串
- 它是一个规则:可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕获到(exec / match…)
编写正则表达式
创建方式有两种:
字面量
构造函数
//=>字面量创建方式(两个斜杠之间包起来的,都是用来描述规则的元字符)
let reg1 = /\d+/g;
//=>构造函数模式创建 两个参数:元字符字符串,修饰符字符串
//=>构造函数因为传递的是字符串,\需要写两个才代表斜杠
let reg2 = new RegExp("\\d+","g");
字面量和构造函数创建的区别:
构造函数创建的正则表达式可以使用变量,比较灵活
let type = "aaa";
//这种情况只能使用构造函数方式(因为它传递的规则是字符串,只有这样才能进行字符串拼接)
reg = new RegExp("^@" + type + "@$");
console.log(reg.test("@aaa@")); //=>true
console.log(reg.test("@aaaa@")); //=>false
总结:
如果想把一个变量的值作为正则元字符的一部分,只能通过构造函数的方式创建正则
正则表达式由两部分组成:
元字符
修饰符
常用元字符:
1.量词元字符,设置出现的次数
* 零到多次
+ 一到多次
? 零次或者一次
{
n} 出现n次
{
n,} 出现n到多次
{
n,m} 出现n到多次
2.特殊元字符,单个或者组合在一起代表特殊的含义
\ 转义字符
. 除\n(换行符)以外的任意字符
^ 以哪一个元字符作为开始
let reg=/^\d/
console.log(res.test("fDGng")) //false
console.log(res.test("2019SFfe")) //true
console.log(res.test("egswb2019")) //false
$ 以哪一个元字符作为结束
let reg=/\d$/
console.log(res.test("frdres")) //false
console.log(res.test("2019sesefe")) //false
console.log(res.test("wfasg2019")) //true
\n 组合元字符,表示换行,\把n转译成了换行符
\d 0-9之间的数字
\D 非0-9之间的数字(大写和小写的意思是相反的)
\w 数字,字母,下划线中的任意一个字符
\W 除所有字母,数组,下划线外的字符(大写和小写的意思是相反的)
\s 一个空白字符(包含空格,制表符,换页符等)=[\r\n\t\v\f]
\S 匹配非空格的字符(大写和小写的意思是相反的)
\t 一个制表符(一个TAB键,四个空格)
\b 匹配一个单词的边界
\B 匹配一个非单词的边界
\n 匹配一个换行符
\r 匹配一个回车符
x|y x或者y中的一个字符
[xyz] x或者y或者z中的一个字符
[^xy] 除了x/y以外的任意字符
[a-z] 指定a-z这个范围中的任意字符 [0-9a-zA-Z_]===\w
[^a-z] 非a-z这个范围中的任意字符
[\u4E00-\u9FA5] 汉字范围
() 正则中的分组符号
(?:) 只匹配不捕获,不让他捕获该子表达式(match,matchAll)
let reg = /(?:b)(c)/
let str = "abcabc"
console.log(str.match(reg)) //["bc", "c"]
console.log(...str.matchAll(reg))//["bc", "c"]
(?=) 正向预查,表示匹配后面紧挨着的字符的字符串
(?!) 反向预查,表示匹配后面不是该字符的字符串
let str = "abcacb"
let reg1 = /a(?=b)/g //匹配a后面紧挨着是b的字符串
let reg2 = /c(?!b)/g //匹配c后面不是b的字符串
console.log(str.match(reg1)) //["a"]
console.log(str.match(reg2)) //["c"]
(?<=pattern)xxx 反向预查,表示匹配紧跟pattern后面的xxx
^$和转义字符\详解:
let reg=/\d+/ //^,$都不加,表示字符串包含符合规则的内容即可
let reg=/^\d+$/ //^,$都加,表示字符串只能是和规则一致的内容
-------------------------------------------------------------------------------------------
let reg=/^2.3$/
console.log(reg.test("2f3")) //true
console.log(reg.test("2.3")) //true
console.log(reg.test("23")) //false
console.log(reg.test("2fsef3")) //false
-------------------------------------------------------------------------------------------
let reg=/^2\.3$/ //加了/(转译字符),只能让他代表小数点
console.log(reg.test("2f3")) //false
console.log(reg.test("2.3")) //true
中括号[ ]详解 :
中括号中出现的字符一般都代表本身的含义
\d在中括号中还是0-9
中括号中不存在多位数
let reg=/^[@+]+$/ //表示@或者+符号,出现一到多次
console.log(reg.test("@@@+++")) //true
let reg=/^[\d]$/ //\d在中括号中还是0-9
console.log(reg.test("\\")) //false
console.log(reg.test("d")) //false
console.log(reg.test("9")) //true
let reg=/^[18]$/ //表示 1或者8 console.log(reg.test("1")) //true
console.log(reg.test("8")) //true
console.log(reg.test("18")) //false
let reg=/^[10-29]$/ //表示 1或者0-2或者9
() | 详解 :
let reg=/^18|29$/ //表示以1开头,以9结尾,中间8或者9
/* 但是我们想要的是18或者19*/
let reg=/^(18|29)$/ //表示只有18或者19符合,其他都不符合
console.log(reg.test("18"))
总结:
直接x|y或存在很乱的优先级问题,一般我们写的时候都是伴随着先括号进行分组,因为小括号能改变处理的优先级

\b,\B详解:
\b是单词边界,具体就是\w与\W之间的位置,也包括\w与^之间的位置,和\w与$之间的位置。
\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。
具体说来就是\w与\w、\W与\W、^与\W,\W与$之间的位置。
let str = "[JS] lesson_01.mp4"
console.log(str.replace(/\b/g, "#")) //[#JS#] #lesson_01#.#mp4#
console.log(str.replace(/\B/g, "#")) //#[J#S]# l#e#s#s#o#n#_#0#1.m#p#4
常用修饰符:imgs
i :ignoreCase 忽略单词大小写匹配
/A/.test(‘lalala’) => false
/A/i.test(‘lalala’) => true
m :multiline 可以进行多行匹配
g :global 全局匹配
gi : 全局匹配+忽略大小写
s : "."默认的是匹配除换行符 \n 之外的任何单字符,加上s之后, "."中包含换行符
全局修饰符g的原理:基于lastIndex索引检索
lastIndex:
是一个可读/写的整数,如果匹配模式中带有g修饰符,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec( ) 和 test( ) 方法用到。lastIndex这个属性可能会踩坑的。会导致有时候返回值出错有时候正确
子表达式 和 反向引用:() \1 \2:
let str = "aaabbbcccddd"
let reg1 = /(a)\1\1/g //\1表示的是反向引用第1个子表达式 ,\2表示的是反向引用第2个子表达式
let reg2 = /\1(a)/g
console.log(str.match(reg1)) //["aaa"]
console.log(str.match(reg2)) //["a", "a", "a"]
注意:
反向引用要写在子表达式的后面,否则无效
贪婪模式和非贪婪模式(惰性模式):
let str = "kfs{
{fjh}}lses{
{42}}"
let reg1 = /{
{
.*}}/g //贪婪性
let reg2 = /{
{
.*?}}/g //非贪婪性,量词后面加?
console.log(str.match(reg1)) //["{
{fjh}}lses{
{42}}"]
console.log(str.match(reg2)) //["{
{fjh}}","{
{42}}"]
能使用正则的方法:
正则RegExp.prototype上的方法:
-
exec
-
test
字符串String.prototype上支持正则表达式处理的方法:
-
replace
-
match
只能整体捕获,不捕获括号中的内容 -
split
-
matchAll
(新增) match的升级版,除了整体捕获,还可以捕获括号中内容 -
search
exec:
exec() 不开启全局匹配模式g的情况:
let str = "aaa2019ccc2020ccc021";
let reg = /\d+/;
/*
* 基于exec实现正则的捕获
* 1.捕获到的结果是null或者一个数组
* 第一项:本次捕获到的内容
* 其余项:对应小分组本次单独捕获的内容
* index:当前捕获内容在字符串中的起始索引
* input:原始字符串
* 2.每执行一次exec,只能捕获到一个符合正则规则的,但是默认情况下,我们执行一百遍,获取的结果永远都是第一个匹配到的,其余的捕获不到
* =>“正则捕获的懒惰性”:默认只捕获第一个
*/
console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]
console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]
exec() 开启全局匹配模式g的情况:
let str = "aaa2019ccc2020ccc2021";
let reg = /\d+/g;
console.log(reg.exec(str)); //=>["2019", index: 7, input: "aaa2019ccc2020ccc021"]
console.log(reg.exec(str)); //=>["2020", index: 10, input: "aaa2019ccc2020ccc021"]
exec()和test()API与lastIndex的关系:
exec: 当exec使用修饰符g进行匹配时,内部会基于lastIndex机制去匹配
let str = "aaa2019ccc2020ccc021";
/*
* reg.lastIndex:当前正则下一次匹配的起始索引位置
* 懒惰性捕获的原因:默认情况下lastIndex的值不会被修改,每一次都是从字符串开始位置查找,所以找到的永远只是第一个
* */
let reg = /\d+/;
console.log(reg.lastIndex); //=>0 下面匹配捕获是从STR索引零的位置开始找
console.log(reg.exec(str)); //=> ["2019", index: 3, input: "aaa2019ccc2020ccc021"]
console.log(reg.lastIndex); //=>0 第一次匹配捕获完成,lastIndex没有改变,所以下一次exec依然是从字符串最开始找,找到的永远是第一个匹配到的
-------------------------------------------------------------------------------------------------------------------------------------
let str = "aaa2019ccc2020ccc2021";
//使用全局匹配模式g,内部会基于lastIndex机制去匹配
let reg = /\d+/g;
console.log(reg.exec(str)); //=>["2019"...]
console.log(reg.lastIndex); //=>7 设置全局匹配修饰符g后,第一次匹配完,lastIndex会自己修改
console.log(reg.exec(str)); //=>["2020"...]
console.log(reg.lastIndex); //=>14
console.log(reg.exec(str)); //=>["2021"...]
console.log(reg.lastIndex); //=>20
console.log(reg.exec(str)); //=>null 当全部捕获后,再次捕获的结果是null,但是lastIndex又回归了初始值零,再次捕获又从第一个开始了...
console.log(reg.lastIndex); //=>0
console.log(reg.exec(str)); //=>["2019"...]
test() 开启g同理:基于lastIndex查找
let reg = /\d/
console.log(reg.test("3")) //true
console.log(reg.lastIndex) //0
console.log(reg.test("3")) //true
console.log(reg.lastIndex) //0
console.log(reg.test("3")) //true
console.log(reg.lastIndex) //0
-----------------------------------------------------------
let reg = /\d/g
console.log(reg.test("3")) //true
console.log(reg.lastIndex) //1
console.log(reg.test("3")) //false
console.log(reg.lastIndex) //0
console.log(reg.test("3")) //true
console.log(reg.lastIndex) //1
test使用修饰符g消除lastIndex机制的解决方法:
-
不使用g去test()
-
每次创建一个新的正则去test
console.log(/\d/g.test("3")) //true
console.log(/\d/g.test("3")) //true
console.log(/\d/g.test("3")) //true
lastIndex的值可读可写,手动重写为0
let reg = /\d/g
console.log(reg.test("3")) //true
reg.lastIndex = 0
console.log(reg.test("3")) //true
reg.lastIndex = 0
console.log(reg.test("3")) //true
reg.lastIndex = 0
match()方法:
字符串中的MATCH方法,可以在执行一次的情况下,捕获到所有匹配的数据(前提:正则也得设置G才可以
不加g:
大正则和小分组都能拿到,但是只能捕获一次且每次捕获都需要从头开始加了g后:
全局可捕获多次,但只能把大正则捕获的信息拿到,小分组就拿不到了
console.log("aaa2019bbb2020ccc2021".match(/\d+/)); //["2019", index: 3, input: "aaa2019bbb2020ccc2021"]
console.log("aaa2019bbb2020ccc2021".match(/(\d)+/)); //["2019", "9", index: 3, input: "aaa2019bbb2020ccc2021"]
console.log("aaa2019bbb2020ccc2021".match(/\d+/g)); //["2019", "2020", "2021"]
console.log("aaa2019bbb2020ccc2021".match(/(\d)+/g)); //["2019", "2020", "2021"]
总结:
match方法可以看做是exec的升级版,exec开启g后需要基于lastIndex调用多次才能匹配上所有满足的字符串,而match方法开启g后,一次调用就能全部匹配出来
使用exec封装一个match方法:
~ function () {
function execAll(str = "") {
//str:要匹配的字符串
//验证当前正则是否设置了G,不设置则不能在进行循环捕获了,否则会导致死循环
if (!this.global) return this.exec(str);
//ARY存储最后所有捕获的信息 RES存储每一次捕获的内容(数组)
let ary = [],
res = this.exec(str);
while (res) {
//把每一次捕获的内容RES[0]存放到数组中
ary.push(res[0]);
//只要捕获的内容不为NULL,则继续捕获下去
res = this.exec(str);
}
return ary.length === 0 ? null : ary;
}
RegExp.prototype.execAll = execAll;
}();
let str = "aaa2019bbb2020ccc2021"
console.log(str.match(/\d+/g)); //["2019", "2020", "2021"]
console.log(/\d+/g.execAll(str)); //["2019", "2020", "2021"]
matchAll:
可以看成是match的升级版,并且RegExp必须是设置了全局模式g,否则会抛出异常TypeError,返回值是一个可迭代对象,可通过...展开,也可以通过for of 遍历
matchAll比match的优势在于:
- 多了可以匹配括号中的内容,而match不行,只会整体捕获
- 整体匹配比match多很多信息,比如索引
let reg = /\d(\d)/g
let str = "my 34dg grd42 name is tom "
console.log(str.match(reg)) //["34", "42"]
console.log(...str.matchAll(reg))
//["34", "4", index: 3, input: "my 34dg grd42 name is tom "]
//["42", "2", index: 11, input: "my 34dg grd42 name is tom "]
split:
let str = "11aa122aa233aa4";
console.log(str.split("aa")) //["11", "122", "233", "4"]
console.log(str.split(/[a-z]+/)) //["11", "122", "233", "4"]
console.log(str.split(/([a-z])+/)) //["11", "a", "122", "a", "233", "a", "4"]
search:
let str = "11aa122aa233aa4";
console.log(str.search("aa")) //2
console.log(str.search(/[a-z]+/)) //2
replace: 我认为最强大的方法,第二个参数传入一个函数,可以处理很多问题
let str = '2018aaaa@2019aaaa@2020'
//需求:把'aaaa'都替换成空格
// 1.不用正则执行一次只能替换一个,执行多次后才能全部替换
console.log(str.replace('aaaa', ' ')) //2018 @2019aaaa@2020
//2.使用正则
console.log(str.replace(/aaaa/, ' ')) //2018 @2019aaaa@2020
console.log(str.replace(/aaaa/g, ' ')) //2018 @2019 @2020
replace的反向引用:
let str = "aabb ddff sge"
let reg1 = /(\w)\1(\w)\2/g
// $& :表示匹配的全部字符串1
// $1 : 表示匹配到的第一个括号的引用
// $2 : 表示匹配到的第二个括号的引用
let newStr = str.replace(reg1,"$2$2$1$1")
let newStr2 = str.replace(reg1,"$&")
console.log(newStr) //bbaa ffdd sgeclike
console.log(newStr2) //aabb ddff sge
let str2 = str.replace(reg1,($,$1,$2)=>{
// $:整体匹配到的内容
// $1:第一个括号匹配到的内容
// $2:第二个括号匹配到的内容
console.log($,$1,$2) //aabb a b //ddff d f
// 需要return
})
console.log(str2) //undefined undefined sge
正则的分组捕获:
let str = "111222334";
let reg = /^(\d{3})(\d{3})(\d{2})(\d{1})$/;
console.log(reg.exec(str)); //["111222334", "111", "222", "33", "4", index: 0, input: "111222334"]
console.log(str.match(reg)); //["111222334", "111", "222", "33", "4", index: 0, input: "111222334"]
//=>第一项:大正则匹配的结果
//=>其余项:每一个小分组单独匹配捕获的结果
//=>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理
正则的应用:个人经验
时间字符串的格式化处理
function formatTime(template = '{0}年{1}月{2}日 {3}时{4}分{5}秒') {
//1.首先获取时间字符串中的年月日,小时分钟秒等信息
let timeAry = this.match(/\d+/g) // ["2019", "8", "13", "16", "51", "3"]
template = template.replace(/\{(\d+)\}/g, (content, $1) => {
//content:当前本次大正则匹配的信息
//$1:本次小分组单独匹配的信息
//$1的值作为索引,到time-ary中找到对应的时间(如果没有则用“00”补)
let time = timeAry[$1] || "00"
time.length < 2 ? time = '0' + time : null
return time
})
return template
}
String.prototype.formatTime = formatTime
let time = "2019-8-13 16:51:3"
console.log(time.formatTime()) //2019年08月13日 16时51分03秒
地址中查询字符串和HASH的提取
function queryURL() {
let obj = {
}
this.replace(/([^?#&=]+)=([^?#&=]+)/g, (...arg) => {
let [, $1, $2] = arg
obj[$1] = $2
})
this.replace(/#([^?#&=]+)/g, (...arg) => {
let [, $1] = arg
obj['HASH'] = $1
})
return obj
}
String.prototype.queryURL = queryURL
let urlr = 'www.baidu.com/?lx=1&from=weixin&name=zhangsan#video'
//{lx: "1", from: "weixin", name: "zhangsan", HASH: "video"}
正则之千分符
function millimeter() {
res = /\d{1,3}(?=(\d{3})+$)/g
// return this.replace(res, (content) => {
// return content + ","
// })
//简写
return this.replace(res,"$&,")
}
String.prototype.millimeter = millimeter
let num = '156845842412343245223' //156,845,842,412,343,245,223
console.log(num.millimeter())
小驼峰写法
//the-first-time 变成小驼峰写法
let reg4 = /-(\w)/g
console.log("the-first-time".replace(reg4, function ($, $1) {
return $1.toUpperCase()
})) //theFirstTime
字符串去重
let reg5 = /(\w)\1*/g
console.log("aaaaaabbbbbbcccccc".replace(reg5, function ($, $1) {
return $1
})) //abc
敏感词过滤
let str = /你妈|恢复/g
console.log(..."你妈看能恢复护文妈妈 ,附件你妈".replace(str, "**"))
//* * 看 能 * * 护 文 妈 妈 , 附 件 * *
反转
let reg3 = /(\w)\1(\w)\2/
console.log("aabb".replace(reg3, "$2$2$1$1")) //bbaa
解析html模板提取信息
let str = `
<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>
`
let reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>.*?<\/li>/sg
//matchAll相当于exec的循环到底
let result = str.matchAll(reg)
console.log(...result)