正则表达式
正则的匹配规则:
正则表达式是匹配模式,要么匹配字符,要么匹配位置
精准匹配
精准匹配字符串中的某个子串
const regex = /hello/
console.log(regex.test('helloxxx'))
//true
模糊匹配
横向模糊:
横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。
其实现的方式是使用量词。譬如{m,n},表示连续出现最少m次,最多n次。
比如/ab{2,5}c/表示匹配这样一个字符串:第1个字符是“a”,接下来是2到5个字符“b”,最后是字符“c”
其中g是修饰符,表示全局
const regex = /ab{2,5}c/g
var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";
console.log(string.match(regex))
// [ 'abbc', 'abbbc', 'abbbbc', 'abbbbbc' ]
纵向模糊查询
纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。
其实现的方式是使用字符组。譬如[abc],表示该字符是可以字符“a”、“b”、“c”中的任何一个
字符组
范围表示法
比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]。用连字符-来省略和简写。
//范围表示法
const regex = /[0-28-9]/
//0-2 或者 8-9
const string = '6'
console.log(regex.test(string))
// false
因为连字符有特殊用途,那么要匹配“a”、“-”、“z”这三者中任意一个字符,该怎么做呢?
不能写成[a-z],因为其表示小写字符中的任何一个字符。
可以写成如下的方式:[-az]或[az-]或[a\-z]。即要么放在开头,要么放在结尾,要么转义。总之不会让引擎认为是范围表示法就行了。
纵向模糊匹配,还有一种情形就是,某位字符可以是任何东西,但就不能是"a"、"b"、"c"。
此时就是排除字符组(反义字符组)的概念。例如[^abc],表示是一个除"a"、"b"、"c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念。
当然,也有相应的范围表示法
常见的简写
\d就是[0-9]。表示是一位数字。记忆方式:其英文是digit(数字)。
\D就是[^0-9]。表示除数字外的任意字符。
\w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。
\W是[^0-9a-zA-Z_]。非单词字符。
\s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。
\S是[^ \t\v\n\r\f]。 非空白符。
.就是[^\n\r\u2028\u2029]。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。
可以使用[\d\D]、[\w\W]、[\s\S]和[^]中任何的一个
量词的简写形式
{m,} 表示至少出现m次。
{m} 等价于{m,m},表示出现m次。
? 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗?
+ 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。
* 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。
const regex = /\d{3,10}/g
// 数字必须出现3-10次
const string = '123'
console.log(regex.test(string))
const regex = /\d+/g
//至少出现一次
const string = '1adas'
console.log(regex.test(string))
贪婪匹配
默认匹配是贪婪匹配,能匹配到多少就匹配多少
// 贪婪匹配
const regex = /\d{2,5}/g
var string = "123 1234 12345 123456";
console.log(string.match(regex))
// [ '123', '1234', '12345', '12345' ]
它会尽可能多的匹配。你能给我6个,我就要5个。你能给我3个,我就3要个。反正只要在能力范围内,越多越好
惰性匹配
其中/\d{2,5}?/表示,虽然2到5次都行,当2个就够的时候,就不在往下尝试了
//惰性匹配
const regex = /\d{2,5}?/g
var string = "123 1234 12345 123456";
console.log(string.match(regex))
// [
// '12', '12', '34',
// '12', '34', '12',
// '34', '56'
// ]
多支选择
一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。
具体形式如下:(p1|p2|p3),其中p1、p2和p3是子模式,用|(管道符)分隔,表示其中任何之一
// 多选分支
const regex = /good|nice/g
const string = 'good nicexxx'
console.log(string.match(regex))
// [ 'good', 'nice' ]
const regex = /goodnice|good|nice/g
// [ 'goodnice' ]
多选分支是惰性匹配,当前的匹配上了,后面就不会再匹配了
练习
// test
// 匹配十六进制颜色
const regex = /#[0-9a-fA-F]{3,3}|#[0-9a-fA-F]{6,6}/g
const string = '#ffbbad #Fc01DF #FFF #ffE'
console.log(string.match(regex))
const regex = /^(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9]|0?[0-9])$/g
//匹配二十四小时制时间
const regex = /^[0-9]{4}-(((0?[1,3,5,7,8,]|1[0,2])-(0?[1-9]|[1-2][1-9]|3[0-1]))|((0?[4,6,9]|11)-(0?[1-9]|[1-2][1-9]|30))|(0?2-(0?[1-9]|[1-2][1-8])))$/
//日期表达式,没有考虑闰年的情况
var string = '<div id="container" class="main"></div>';
const regex = /id=".*?"/g
//前面是id=",后面匹配任意字符串,*出现任意次,可以出现跟不出现,最后必须以”结尾,?表示不进行贪婪匹配
console.log(string.match(regex))
//[ 'id="container"' ]
正则表达式的位置匹配
正则表达式是匹配模式,要么匹配字符,要么匹配位置!!!
位置
位置是相邻字符串之间的位置
匹配位置的方法
在ES5中,共有6个锚字符:
^ $ \b \B (?=p) (?!p)
^:
(脱字符)匹配开头,在多行匹配中匹配行开头
$:
(美元符号)匹配结尾,在多行匹配中匹配行结尾
比如我们把字符串的开头和结尾用"#"替换(位置可以替换成字符的!):
var result = "hello".replace(/^|$/g, '#');
console.log(result);
// => "#hello#"
多行匹配模式时,二者是行的概念,这个需要我们的注意:
var result = "I\nlove\njavascript".replace(/^|$/gm, '#');
console.log(result);
/*
#I#
#love#
#javascript#
*/
\b和\B
\b是单词边界,具体就是\w和\W之间的位置,
也包括\w和$之间的位置
const regex = /\b/g
var result = "[JS] Lesson_01.mp4"
console.log(result.replace(regex,'#'))
// [#JS#] #Lesson_01#.#mp4#
首先,我们知道,\w是字符组[0-9a-zA-Z_]的简写形式,即\w是字母数字或者下划线的中任何一个字符。而\W是排除字符组[^0-9a-zA-Z_]的简写形式,即\W是\w以外的任何一个字符。
此时我们可以看看"[#JS#] #Lesson_01#.#mp4#"中的每一个"#",是怎么来的。
- 第一个"#",两边是"["与"J",是
\W和\w之间的位置。 - 第二个"#",两边是"S"与"]",也就是
\w和\W之间的位置。 - 第三个"#",两边是空格与"L",也就是
\W和\w之间的位置。 - 第四个"#",两边是"1"与".",也就是
\w和\W之间的位置。 - 第五个"#",两边是"."与"m",也就是
\W和\w之间的位置。 - 第六个"#",其对应的位置是结尾,但其前面的字符"4"是
\w,即\w和$之间的位置。
\B就是\b的反面的意思,非单词边界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。
const regex = /\B/g
var result = "[JS] Lesson_01.mp4"
console.log(result.replace(regex,'#'))
//#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4
(?=p)和(?!p)
(?=p),其中p是一个子模式,即p前面的位置。
比如(?=l),表示'l'字符前面的位置,
先行断言
const regex = /(?=l)/g
let string = 'hello'
console.log(string.replace(regex,'#'))
// he#l#lo
(?!p)就是(?=p)的反而意思
即先行否定断言
const regex = /(?!l)/g
let string = 'hello'
console.log(string.replace(regex,'#'))
// #h#ell#o#
在ES5中,只支持这两种断言方式,而在ES6中,还可以使用后行断言和后行否定断言
这几个全都是进行位置的匹配
ES6新增:(?<=p),(?<!p)
const regex = /(?<=l)/g
let string = 'hello'
console.log(string.replace(regex,'#'))
// hel#l#o
const regex = /(?<!l)\d{1,}/g
//后行否定断言提取非l后面的数字
let string = 'hello123456'
console.log(string.match(regex))
位置的特性
对于位置,可以理解为空字符串
"hello" == "" + "h" + "" + "e" + "" + "l" + "" + "l" + "o" + "";
"hello" == "" + "" + "hello"
因此,把/^hello$/写成/^^hello?$/,是没有任何问题的:
字符串之间的位置,可以写成多个
所以在使后行否定断言时会匹配字符串开头的位置
#h#e#llo#
练习
不匹配任何任何东西的正则
/.^/
数字的千位分隔符表示法
const regex = /(?!^)(?=(\d{3}){1,}$)/g
//??????????????????????????????????
//前面的先行否定断言是排除掉开头加.的情况
const string = '123456789'
console.log(string.replace(regex,'.'))
console.log(string.match(regex))
匹配密码
//验证密码强度
const regex = /(?=.*[0-9])^([0-9a-zA-z]){6,12}$/g
//匹配密码,必须包含数字
// const regex2 = /(?=.*[0-9])/g
//位置匹配。匹配任意字符串及后面的数字的位置
const password = '1aagfdfsg'
// console.log(regex2.test(password))
console.log(password.match(regex))
const regex = /(?!^[0-9]{6,12}$)^([0-9a-zA-z]){6,12}$/g
//匹配密码,必须包含数字,但又不能全是数字
正则表达式括号的作用
括号的作用,其实三言两语就能说明白,括号提供了分组,便于我们引用它。
引用某个分组,会有两种情形:在JavaScript里引用它,在正则表达式里引用它
分组和分支结构
分组
/a+/匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用/(ab)+/
其中括号是提供分组功能,使得题词+作用于‘ab’这个整体
const regex = /(ab)+/g
const string = 'ababab ababac acabac'
console.log(string.match(regex))
// [ 'ababab', 'abab', 'ab' ]
分支结构
在多选分支结构(p1|p2)中,此处括号的作用也是不言而喻的,提供了子表达式的所有可能
const regex = /I Love (hello|world)/g
const string = 'I Love hello I Love world'
console.log(string.match(regex))
// [ 'I Love hello', 'I Love world' ]
引用分组
引用分组可以对数据进行提取,以及更好用的替换操作
提取数据
// 使用引用分组提取日期
const regex = /(\d{4})-(0[0-9]|1[0-2])-([0-2][0-9]|3[0-1])/
const string = '2022-01-11'
console.log(string.match(regex))
/*
[
'2022-01-11',
'2022',
'01',
'11',
index: 0,
input: '2022-01-11',
groups: undefined
]
*/
//在使用match时不用全局匹配可以拿到具体每个引用分组()里面的值
//也可以使用正则对象的exec方法
console.log(regex.exec(string))
//返回的结果一致
match返回的是一个数组,第一个元素是整体的匹配结果,然后是各个分组匹配的内容。然后是匹配下标,最后是输入文本
替换
其中replace中的,第二个参数里用$1、$2、$3指代相应的分组
const regex = /(\d{4})-(0[0-9]|1[0-2])-([0-2][0-9]|3[0-1])/
const data = '2021-01-11'
console.log(data.replace(regex,"$2-$3-$1"))
console.log('2011-01-03'.replace(regex,'$1\$3\$2'))
console.log(RegExp.$1)
// 也可以使用构造函数的全局属性$1至$9来获取
// 会拿到最近一次使用正则表达式的地方的
反向引用
除了使用相应API来引用分组,也可以在正则本身里引用分组。但只能
引用之前出现的分组,即反向引用
比如要写一个正则支持匹配如下三种格式:
2016-06-12
2016/06/12
2016.06.12
// var regex = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/;
//错误示范
// 虽然匹配了要求的情况,但也匹配"2016-06/12"这样的数据
const regex = /(\d{4})(-|\/|\.)(0[0-9]|1[0-2])\2([0-2][0-9]|3[0-1])/
const data = "2017-06-12"
console.log(data.match(regex))
注意里面的\2,表示的引用之前的那个分组(-|/|.)。不管它匹配到什么(比如-),\2`都匹配那个同样的具体某个字符
括号嵌套
var regex = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = "1231231233";
console.log( regex.test(string) ); // true
console.log( RegExp.$1 ); // 123
console.log( RegExp.$2 ); // 1
console.log( RegExp.$3 ); // 23
console.log( RegExp.$4 ); // 3
第一个字符是数字,比如说1,
第二个字符是数字,比如说2,
第三个字符是数字,比如说3,
接下来的是\1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是123,
接下来的是\2,找到第2个开括号,对应的分组,匹配的内容是1,
接下来的是\3,找到第3个开括号,对应的分组,匹配的内容是23,
最后的是\4,找到第3个开括号,对应的分组,匹配的内容是3。
引用不存在的分组
因为反向引用,是引用前面的分组,但我们在正则里引用了不存在的分组时,此时正则不会报错,只是匹配反向引用的字符本身。例如\2,就匹配"\2"。注意"\2"表示对"2"进行了转意
非捕获分组
之前出现的分组,都会捕获它们匹配到的数据,以便后续引用,因此也称他们是捕获型分组。
如果只想要括号最原始的功能,但不会引用它,即,既不在API里引用,也不在正则里反向引用。此时可以使用非捕获分组(?:p)
const regex = /(?:\d{4})(-|\/|\.)(0[0-9]|1[0-2])\1([0-2][0-9]|3[0-1])/
const data = "2017-06-12"
console.log(data.match(regex))
练习
//模拟trim方法
const regex = /^\s+|\s+$/g
console.log(' replace '.replace(regex,''))
评论区