正则表达式
本博客使用的是JavaScript举的栗子
如有错误还请在评论区指出
为什么要学习正则表达式?
学习了正则表达式可以装一下,可以让你跟其他普通程序员拉开差距,,可以涨工资!!!懂了为什么要学了吧?
其实正则表达式的作用也就下面这几种常用
- 查找
- 验证
- 分割
- 替换
举一个简单的例子:
将一个字符串中的数字找出来放到一个数组中去。
例: 输入: “aaaa1111aaa11a33f333122”
输出: [1111,11,33,333122]
一般人的解法:
function getNumber(str){
let arr = [];
let temp = "";
for(let i=0;i<str.length;i++){
if(!isNaN(str[i])){
temp += str[i];
}else{
if(temp!=="")
arr.push(parseInt( temp ));
temp = "";
}
}
if(temp!=="")
arr.push(parseInt( temp ));
return arr;
}
console.log(getNumber("aaaa1111aaa11a33f333122"));
输出:
[1111,11,33,333122]
怎没样? 可能有百分之八十的人都是这样想的。
会正则表达式的神人解法:
let reg = /\d+/g;
let arr = "aaaa1111aaa11a33f333122".match(reg);
console.log(arr);
输出:
[1111,11,33,333122]
有没有眼前一亮,相见恨晚的感觉???,看懂掌声。。。。
好了,下面我们先来简单学习一下正则表达式,先从声明开始。
简单的声明
用上边的全局匹配数子举栗子
字面量声明:
let regex = /\d+/g;
简单吧?但是要注意啊,声明的时候只能使用字符常量,不能使用变量
构造函数声明:
let regex = new RegExp(/\d+/g)
或者
let regex = new RegExp("\\d+","g")
看到双斜杠了没????? 因为要转义啊,兄弟,let regex = new RegExp("\d+", "g") 这样不转义 匹配到的就是字母 “d”了。
下面介绍几个正则匹配的方法,就是像上面使用match匹配一样的几个方法
正则匹配方法
正则对象下面的方法:
- test
- exec
字符串下面的方式:
- split
- search
- match
- replace
Regex.text()
说明:这个方法会检测字符串中是否匹配到了正则表达式
返回: true 或 false
用法:
let regex = /\d+/g;
regex.test("12ddd"); //true
regex.test("ddd"); //false
Regex.exec()
说明:一个在字符串中查找匹配到了正则表达式的方法 ,首次调用会匹配到第一个,再次调用会匹配到下一个
返回:匹配到返回一个数组,无匹配返回null
返回数组结构:MDN文档连接传送门
用法:
let regex = /\d+/g;
regex.exec("1d1d1d1") //["1", index: 0, input: "1d1d1d1", groups: undefined]
regex.lastIndex // 1
regex.exec("1d1d1d1") // ["1", index: 2, input: "1d1d1d1", groups: undefined]
regex.lastIndex // 3
regex.exec("1d1d1d1") // ["1", index: 4, input: "1d1d1d1", groups: undefined]
regex.lastIndex // 5
regex.exec("1d1d1d1") // ["1", index: 6, input: "1d1d1d1", groups: undefined]
regex.lastIndex // 7
regex.exec("1d1d1d1") //null
上面的例子中经过次调用regex.exec后返回的结果都不相同,其中index表示匹配到的字符串在原始字符串中开始的下标,其中可以使用regex.lastIndex来查看下一次开始匹配的位置。
String.split()
说明:配合正则表达式会以匹配正则的方式来分割字符串
返回:返回分割后的数组,如果第一个或者最后一个匹配到则返回的数组前一个或者最后一个会是一个空的字符串
例子:
let regex = /\d+/;
"1a2b3d4e5f".split(regex) // ["", "a", "b", "d", "e", "f"]
String.search()
说明:返回匹配到的第一个符合正则表达式的字符的下标
返回: 匹配到的第一个字符的下标
let regex = /\d+/;
"a2b113d4e5f".search(regex) //1
String.match()
说明:如果不是全局匹配则返回一个数组(跟exec返回的数组相近),如果是全局匹配就以数组的形式返回匹配到的字符
返回:如果不是全局匹配就返回跟exec返回相同的数组,如果是全局匹配,则以数组的形式返回所有匹配到的结果
let regex = /\d+/i //不是全局匹配
"a2b113d4e5f".match(regex) //["2", index: 1, input: "a2b113d4e5f", groups: undefined]
let regex = /\d+/g
"a2b113d4e5f".match(regex) // ["2", "113", "4", "5"]
String.replace()
说明:替换正则匹配到的字符
返回:返回替换后的字符串
let regex = /\d+/g
"a2b113d4e5f".replace(regex,"*") //"a*b*d*e*f"
replace的第二个参数还是回调函数
let regex = /\d+/g
"a2b113d4e5f".replace(regex,function(str){
console.log(str) //拿到匹配到的字符 2 113 4 5
return "*" //返回要替换的字符
}) //"a*b*d*e*f"
好了好了,以上是一些我们为什么要学,还有就是一些常见用法,接下来就是重头戏了,我们来说说元字符
元字符
我打算分为四部分:
- 字符相关
- 数量相关
- 位置相关
- 括号相关
字符相关
字符相关的一共有: \w \W \d \D \s \S . 这几个,然后我们依次介绍
-
\w
这个是斜杠小达不溜,匹配的是字母,数子,下划线,举个栗子
let regex = /\w/g
regex.test("a_1") //true 因为它匹配到了字母数字下划线
regex.test("%%") //false 没有匹配上任何数子,字母,下划线
-
\W
这个是斜杠大达不溜,匹配的是非字母,数字,下划线
例子:略
-
\d
这个是匹配数子的,举个栗子
let regex = /\w/g
regex.test("a_1") //true 因为它匹配到了 1
- \D
这个是匹配非数字的,栗子:略
- \s
这个是匹配空格的
let regex = /\s/g
regex.test("d d") //true 因为字符中间有空格
- \S
这个是匹配非空格的,如果没有空格就匹配成功
let regex = /\S/g
regex.test("d d") //true 因为字符中间有非空格的字符d
regex.test(" ") //false 因为里面只有空格,所以返回false
- .
这个是"点",默认匹配除换行符外的任意字符
let regex = /./g
regex.test(" ") //true 匹配到了空格
regex.test("\n") //false 里面只有一个换行,所以没有匹配到的字符,返回false
以上说了这么多,其实也就需要记四个,而且很好记,\w匹配字母数字下划线,可以理解为w代表的是word,所以匹配的是单词,W 大写的W与w相反,所以就是非字母数字下划线, \d匹配的数子,就是digit(数子), \s就是 space(空间),我也是被我舍友提醒,醍醐灌顶.
数量相关
{} 可以用来匹配一定数量的字符,少废话,上栗子
栗子:
现有字符串 : string = “abbbbbbcc”
如果说要匹配 字符中的六个b的话,可以这样写
let regex = /bbbbbb/g //太傻,不符合程序员的职业操守
string.match(regex) //["bbbbbb"]
let regex = /b{6}/g
string.match(regex) //["bbbbbb"]
这样就可以很方便的匹配一定数量的字符,{}中填写的数子,为想要匹配到的字符的个数,也可以填写范围
let regex = /b{1,3}/g
string.match(regex) // ["bbb", "bbb"]
上面这个栗子,我们写为匹配到三个b,就可以匹配两个bbb,因为这个匹配是贪婪匹配,所以会以最多的方式来匹配,要是不想使用贪婪匹配,也可以改写成惰性匹配
let regex = /b{1,3}?/g
string.match(regex) // ["b", "b", "b", "b", "b", "b"]
在后面加一个?就可以是以最少的匹配了
当然 也有很多中匹配方式,比如匹配一个或者多个regex = /b{1,}/这样就是匹配一个或者多个,第二个参数为空,然后以逗号分隔,就是有上限没下限,懂我意思吧???(疯狂暗示,哈哈哈)
因为在匹配数量的时候, 匹配特定数量的情况会比较多,所以就有了一些简写形式,也可以理解为是语法糖
//匹配一个或者多个
let regex = /b{1,}/
//等同于
let regex = /b+/
//匹配零个或多个
let regex = /b{0,}/
//等同于
let regex = /b*/
//匹配零个或者一个
let regex = /b{0,1}/
//等同于
let regex = /b?/
位置相关
位置的话无非就是 : 开头, 结尾 , 字符边界
开头
let regex = /^\w/g //^表示是字符串开头 这个意思就是匹配字符串开头的那个字符
"adad".match(regex) // ["a"]
结尾
let regex = /\w$/g //$表示是字符串结尾 这个意思就是匹配字符串结尾的那个字符
"adad".match(regex) // ["d"]
边界符\b 和 非边界符\B
一个栗子说明
边界的意思就是 两边是否有 字母,数字,下划线,如果有的话就说明无边界 ,没有的话就是有边界
let regex1 = /\bis\b/i
let regex2 = /\Bis\b/i
"this is a handsome blog".match(regex1) //["is", index: 5, input: "this is a handsome blog", groups: undefined]
"this is a handsome blog".match(regex2) //["is", index: 2, input: "this is a handsome blog", groups: undefined]
上面这个例子中 如果要匹配 is 的 "is"的话就要跟 this中的"is"区分开,因为this中的"is"是有边界的(左边有边界,右边无边界),而 is 中的 "is"是没有边界的 (左边和右边都是空格,所以无边界),所以使用 \b 和 \B区分开,\b表示无边界 \B表示有边界,所以:
let regex1 = /\bis\b/i //匹配两边都没有边界的"is"
let regex2 = /\Bis\b/i //匹配左边有边界,右边无边界的"is"
括号相关
正则里的括号分为 : { } , [ ] , ( )
{ }在上面已经说过了,是跟数量相关的,这里就不说了.
( ):
分组:
let string = "abababa"
let regex = /ababab/g
string.match(regex) //["ababab"]
上面的例子中我们想要匹配 “ababab”字符,但是这样写就很麻烦,所以我们就可以使用 ( )来进行分组
let string = "abababa"
let regex = /(ab){3}/g //如此写法便可简洁明了
string.match(regex) //["ababab"]
提取值:
比如说有一个字符串 “alkasnc2020-12-06dadas”如果我们想要匹配之中的 2020-12-06的话,我们的正则表达式可能会这样写 : let regex = /\d{4}-\d{2}-\d{2}/g,但是如果我们不仅要匹配,还要拿到当中的 年,月,日的话,可能就需要再把匹配到的东西再进行处理,但是如果用到 ( )的提取值得功能得话,可能就会比较简单了
let string = "alkasnc2020-12-06dadas"
let regex = /\d{4}-\d{2}-\d{2}/i;
string.match(regex); //["2020-12-06", index: 7, input: "alkasnc2020-12-06dadas", groups: undefined]
//提取其中得年月日得方法;
let string = "alkasnc2020-12-06dadas"
let regex = /(\d{4})-(\d{2})-(\d{2})/i;
string.match(regex); // ["2020-12-06", "2020", "12", "06", index: 7, input: "alkasnc2020-12-06dadas", groups: undefined]
RegExp.$1 // "2020"
RegExp.$2 // "12"
RegExp.$3 // "06"
从返回得结果里面我们可以看出来,通过提取值得方式返回得数组中,有我们正则表达式中使用( )来划分得值,其中得到得 年,月,日 可以通过全局得Regexp.$1,Regexp.$2,Regexp.$3来获取
另外补充一下 分组命名:
let str = "2020-01-06";
let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
result = str.match(reg);
result.groups // {year: "2020", month: "01", day: "06"}
还有一种获取匹配到的分组结果的方法,我们直接通过例子来说明
将 “2020-12-06” 转变为 ”06/12/2020“ ,意思就是将 ”年-月-日“转变为 ”日/月/年“
let string = "2020-12-06"
let regex = /(\d{4})-(\d{2})-(\d{2})/
string.replace(regex,"$3/$2/$1") //"06/12/2020"
//或者是
string.replace(regex,(string,year,month,day)=>{
//这里的参数 第一个:匹配到的字符串 后三个为分组的结果
return `${day}/${month}/${year}` //"06/12/2020"
})
反向引用:
有时候我们需要之前已经匹配到的值匹配的时候就需要使用反向引用
要匹配字符串: ”new-content-nav“ 或者 ”new_content_nav” 但是不能匹配 “new_content-nav” 和 ”new-content_nav“ 就是前面用什么连接的两个单词后面也得用什么连接
let string = "new-content-nav";
let regex = /\w{3}(-|_)\w{7}(\1)\w{3}/g; //这里使用 \w{7}(\1) 的里面的\1 表示使用前面括号中匹配到的第一个字符来匹配当前字符,如果前面匹配到的是—那么这个地方也就使用-,如果前面是_那么这里就是_
string.match(regex) // ["new-content-nav"]
[ ]
字符集合
上才(li)艺(zi)
有字符串: “hi how are you, my name is Awe” ,如果要匹配当中的 are 和 ame
let regex = /a(r|m)e/g
let string = "hi how are you, my name is Awe"
string.match(regex) // ["are", "ame"]
上面的正则表达式中使用(r|m)来表示匹配a 和 e 之间是 r 或 m 的字符 ,同时也可以这样写
let regex = /a[rm]e/g
let string = "hi how are you, my name is Awe"
string.match(regex) // ["are", "ame"]
用中括号来括住 [rm]表示匹配a , e 中间有 [ ]里面的字符的字符串,结果就是 are 和 ame
除了匹配在[]中列出的内容外,还可以匹配一段内容
比如说要匹配 字母 a 到 m
let regex = /[a-m]/g
"sdaqwev".match(regex) // ["d", "a", "e"]
上面的正则表达式通过[ ]来取一段内容,中间用 -来连接 ,就是取字符串里面的 ASCII码在 a 到 m 之间的字符(连边都是闭区间),同样可以取数子 let regex = /[0-9]/g表示匹配数子,相当于 \d,还有一个就是取反 let regex = /[^0-9]/ 前面加一个^表示取反([ ]中括号里面的^),这个正则相当于 \D。
如果说要匹配字母数字下划线就可以这样写 let regex = /[0-9a-zA-Z_]/ 相当于前面介绍的\w
匹配模式
前面说了好多的正则表达式,但是没有提到 正则后面的那个 “g”是啥意思 例如: let regex = /\d/g 里面的g ,
这个意思是表示匹配模式,看我慢慢分解
匹配模式有: g i m s u y
g:表示全局匹配,如果后面不写那就只匹配第一个匹配上的,如果写了就匹配多个,前面的例子有很多,这里就略 , 记法: g表示global
i:表示忽略大小写
let regex = /abc/i
regex.test("ABC") //true 忽略掉了大小写
m:多行匹配
//将所有开头的单词都换成 *
let regex = /^\w/gm;
`aaaa
bbb
ccc`.replace(regex, "*")
/*"*aaa
*bb
*cc"*/
在匹配模式后加上m后会将多行的开头的字母换成 * ,这里要注意如果单单是 /^\w/m/只会换第一行所以要和全局匹配结合。
s: 允许 . 匹配换行符
let str = `abc
efg`;
let reg = /^a.*g$/gs;
reg.test(str); //true
上面这个正则表达式表示:匹配a开头 g结尾中间有零个或多个字符,并且允许中间有换行符
u:表示匹配unicode编码
let str = "a";
let reg = /\u{61}/gu;
reg.test(str); //true
上面的正则表达式表示全局匹配Unicode编码为61的字符 ,匹配到了a
y:粘性匹配
let string = "12345fdafdsa4324";
let regex = /\d/gy;
regex.exec(string) //["1", index: 0, input: "12345fdafdsa4324", groups: undefined]
regex.exec(string) //["2", index: 1, input: "12345fdafdsa4324", groups: undefined]
regex.exec(string) //["3", index: 2, input: "12345fdafdsa4324", groups: undefined]
regex.exec(string) //["4", index: 3, input: "12345fdafdsa4324", groups: undefined]
regex.exec(string) //["5", index: 4, input: "12345fdafdsa4324", groups: undefined]
regex.exec(string) //null
str.match(reg) // ["1", "2", "3", "4", "5"]
上面的代码中使用了粘性匹配,所以只匹配到了前面的12345,没有匹配到后面的4324,所以当多次调用regex.exec(string)的时候 第六次就会返回 null
正零宽断言
有字符串: "iphone3iphone4iphone5iphonenumber";
要求:替换 iPhone3iPhone4iPhone5中的iPhone为苹果,iPhonenumber中的iPhone不替换
let reg = /iphone(?=\d)/g
"iphone3iphone4iphone5iphonenumber".replace(reg,"苹果") //"苹果3苹果4苹果5iphonenumber"
我也不知道咋回事,记下就好。。。。
还有一个否定的 就是只替换 iPhonenumber中的iPhone为苹果
let reg = /iphone(?!\d)/g;
"iphone3iphone4iphone5iphonenumber".replace(reg,"苹果"); //"iphone3iphone4iphone5苹果number"
个人认为(?=\d)中的问号❓是零宽断言的标识,=为后面是 \d表示真 ,! 表示假
负零宽断言
有字符串: '10px20px30pxipx';
要求:替换 '10px20px30px';中的px为像素,ipx中的px不替换
let reg = /(?<=\d+)px/g;
'10px20px30pxipx'.replace(reg,"像素") //"10像素20像素30像素ipx"
同上面的还有一个否定的
let reg = /(?<!\d+)px/g;
'10px20px30pxipx'; .replace(reg,"像素"); //10px20px30pxi像素"
正零宽断言和负零宽断言的区别就是,正向零宽断言前面匹配到的是固定不变的,后面部分会变化,而负向的正好相反
以上就是我所了解的所有关于正则表达式的东西了,满满四千多字啊啊啊啊啊。
还有!!!!