侧边栏壁纸
  • 累计撰写 8 篇文章
  • 累计创建 5 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

正则

郝帅德沃
2023-12-28 / 0 评论 / 0 点赞 / 11 阅读 / 22490 字

正则表达式

正则的匹配规则:

正则表达式是匹配模式,要么匹配字符,要么匹配位置

精准匹配

精准匹配字符串中的某个子串

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),其中p1p2p3是子模式,用|(管道符)分隔,表示其中任何之一

// 多选分支
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,''))
0

评论区