关于前端:正则的扩展详解

48次阅读

共计 5678 个字符,预计需要花费 15 分钟才能阅读完成。

RegExp()

es5 中,RegExp的结构函数参数有两种状况
1、字符串
2、正则表达式

// 第一种状况
let regex = new RegExp('abc', 'i')

// 第二种状况
let regex2 = /abc/i

这两种状况是等价的

let s = 'abc'
regex.test(s) == regex2.test(s); // true

在 es5 中这两种形式不能混用,如果第一个参数是一个正则,第二个是润饰会报错
let regex3 = new RegExp(/abc/,'i') // 报错

es6扭转了这种行为,如果第一个是正则,第二个参数能够应用修饰符,并且返回的正则表达式会疏忽原有的正则表达式修饰符,只应用新指定的修饰符。

let regex4 = new RegExp(/abc/ig, 'i')

在这里,第二个参数 i 会把原有的正则修饰符 ig 笼罩, 最初的后果为/abc/i


u 修饰符

es6对正则表达式增加了 u 修饰符,示意为 ==Unicode 模式 ==,用来解决大于 \uFFFFUnicode字符

/^\uD83D/u.test('\uD83D\uDC2A');  // false

这里加上了 u. 所以变成了 es6 反对,将 \uD83D\uDC2A 转译为一个字符,与 \uD83D 不匹配,所以返回false

/^\uD83D/.test('\uD83D\uDC2A'); // true  

这里没有加 u.es5反对,无奈辨认 4 个字节的 UTF-16 编码。会将 \uD83D\uDC2A 辨认为 两个字符,比对的时候其中的字符匹配,所以返回true


点字符 .

点字符在正则表达式中,示意除了换行符以外的任意单个字符,但对于码点大于 0xFFFF 的字符,点字符串无奈辨认,必须后面加上 u 修饰符

let z = '正'
/^.$/.test(z);   // true
/^.$/u.test(z);  // true


Unicode 字符表示法

es6新增了 大括号 示意 unicode 字符,这种字符在正则中必须应用 u 修饰符能力辨认,否则会被解读为量词,量词是无奈辨认大于 0xFFFFunicode字符的。

/\u{61}/.test('a'); // false
/\u{61}/u.test('a'); // true


i 修饰符

i 修饰符为不辨别大小写,对于大于 0xFFFFunicode的修饰符,前面须要加上 u

/[a-z]/i.test('\u212A'); // false 
/[a-z]/iu.test('\u212A'); // true


y 修饰符

y修饰符,意为粘连(sticky)修饰符
在正则匹配中,g为全局匹配且 没有地位限度
y 在正则匹配中,有地位限度,必须在 == 上一次匹配完后的下一个字符串的第一位开始匹配。==

let a = 'aaa-aa-a'
let r = /a+/y
let r2 = /a+/g
// 第一次匹配
r.exec(a); // aaa
r2.exec(a); // aaa
// 第二次匹配
r.exec(a); // null  
r2.exec(a); // aa  

y为粘连,上一次匹配完后的下一个字符串第一位,第一位是:-无奈匹配上,解决的办法:其实只须要批改一下正则保障每次匹配上就行了 /a+-/y
g 为全局匹配没有地位限度

RegExp.prototype.sticky
来查看是否设置了 y 修饰符

let a1 = /heelo\d/y
console.log(a1.sticky); // true


检测正则表达式修饰符以及注释

  • source 返回正则匹配规定
  • flags 返回正则的修饰符
let rul = /abc/ig
console.log(rul.source); // abc
console.log(rul.flags); // ig


s 修饰符:dotAll 模式

正则表达式中,点 (.) 是一个非凡的字符,代表任意字符,然而有两种例外
第一种:四个字节的 UTF-16 字符,这个应用 u 修饰符解决
第二种:行终止符,应用 s 修饰符解决

行终止符,就是该字符示意一行的终结,上面四个字符属于“行终止符”

  • U+000A 换行符(\n
  • U+000D 回车符(\r
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
 let f = /foo.bar/.test('foo\nbar')
console.log(f); // false // 因为. 不匹配 \n , 所以正则表达式返回 false

// 解决办法 : s 修饰符
/* 应用 s 修饰符,能够使 . 匹配任意单个字符 */
let f2 = /foo.bar/s.test('foo\nbar')
console.log(f2); // true

这种 s 修饰符的模式被称为 dotAll 模式,dot就是代表所有字符。
所以,正则表达式还引入了 dotAll 模式,查看该表达式是否开启了 dotAll 模式。

let f3 = /foo.bar/s
console.log(f3.dotAll); // true  // 开始了 dotAll 模式


后行断言和后行断言

后行断言

后行断言指的是, x 在 y 的后面才匹配
语法:/x(?=y)/ x 在 y 符号前才匹配

let look = '100% 东方不败'
/\d+(?=%)/.exec(look); // 100  // 数字在百分号后面才匹配

let look2 = '100! 东方不败'
console.log(/\d+(?=%)/.exec(look2));  // null 没有匹配到 % 前的数字

后行断言

与后行断言相同, x 在 y 的前面时才匹配
语法:/(?<=y)x/ 阐明: ? 前面跟 <= 条件 y

let look3 = '东方不败 $100 东方求败'
/(?<=\$)\d+/.exec(look3); // 100  示意在 \$ 后的字符才会被匹配

留神 : 后行断言和后行断言的条件是不计入返回后果的


具名组匹配

正则表达式应用圆括号进行组的匹配,每组圆括号代表着一个匹配,匹配后果能够从组中提取进去

let rul = /(\d{4})-(\d{2})-(\d{2})/
let r = rul.exec('2022-12-13')
console.log(r[1]);  // 2022
console.log(r[2]);  // 12
console.log(r[3]);  // 13

尽管后果能够从组中提取进去,但它是通过下标提取的,如果组的程序产生扭转,那么下标也须要做对应更改,其实是不太不便的,对此 es2018 引入了 ” 具名组匹配 ”,能够给组取别名,通过别名提取后果
语法:(?< 别名 > 正则)

let rul2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
let r2 = rul2.exec('2022-12-13')
console.log(r2.groups.year);  // 2022
console.log(r2.groups.month);  // 12
console.log(r2.groups.day);  // 13
console.log(r2.groups.minute);  // undefined  如果没有匹配,那么对象属性为 undefined


解构赋值和替换

通过下面的具名组匹配,给组取别名后,在正则的 groups 中,匹配的属性实际上是一个对象,通过解构赋值能够间接从匹配后果上为变量赋值。

. 能够匹配任意字符,查找单个字符,除了换行和行结束符。
n* 匹配任何蕴含零个或多个 n 的字符串。

解构:

let {groups : {hours,minute}} = /^(?<hours>.*):(?<minute>.*)/.exec('12:49')
console.log(hours,minute);  // 12  49  // 通过解构赋值提取

替换
字符串替换时,应用 $< 组名 > 进行替换

  • 将匹配的字符串地位替换
  • 不影响原正则表达式
let rul4 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u

console.log('2022-12-13'.replace(rul4,'$<day>/$<month>/$<year>')); // 13-12-2022

console.log(rul4);  // /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u

replace()的第二个参数能够是一个函数

let data = '2022-12-13'.replace(rul4,(matched,params1,param2,params3,position,sources,groups)=>{let {day,month,year} = groups;
        return `${day}/${month}/${year}`
       })
console.log(data);   // 13-12-2022

阐明:

  • matched // ‘2022-12-13’ 匹配后果
  • params1 // 第一个组匹配 2022
  • param2 // 第一个组匹配 12
  • params3 // 第一个组匹配 13
  • position // 匹配开始的地位 0
  • sources // 原字符串 2022-12-13
  • groups // 具名组形成的一个对象 {year, month, day}

援用

如果要在正则表达式外部援用某个“具名组匹配”,能够应用 \k< 组名 > 的写法

let str = /^(?<word>[a-z]+)!\k<word>$/
str.test('abc!abc');  // true  援用了具名匹配,前后都是 abc, 前后字符统一
str.test('abc!ab');   // false 援用了具名匹配,前后字符不匹配,然而第二个少一个 c 

其实能够这么了解,援用就是将后面定义好的组,拿过去复用。

数字援用
首先须要明确分组的概念,即正则中的 () 中的内容是一个分组,外面的内容作为一个整体援用。如果在分组前面接上 \ 数字,示意匹配第几个分组

let str2 = /^(\d{2})([a-z]+)\1$/
console.log(str2.test('11abcd'));  // false 
console.log(str2.test('11aa11'));  // true

第一个输入:问题出在结尾是英文,这里结尾应用了 \ 数字1 示意匹配第一个分组,第一个分组是必须是 2 个数字,这里的结尾用的英文所以返回 false
第二个输入:true,其实这时的正则为 /^(\d{2})([a-z]+)(\d{2})$/\1 援用了第一个组

具名匹配的援用和数字援用能够同时应用

let str3 = /^(?<word>[a-z]+)-(\d{2})-\k<word>-\2$/
str3.test('a-11-a-11'); // true
str3.test('a-11-a-aa'); // false

第一个输入:这里应用了援用以及数字援用
第二个输入:false, 结尾通过数字援用后,应该为数字\d{2}


d 修饰符:正则匹配索引

es2022新增 d 修饰符,能够在 exec()match() 的返回后果增加 indices 属性,在该属性上能够拿到匹配的开始地位和完结地位,下标从 0 开始

let text = 'abcdefg'
let te = /bc/d
let result = te.exec(text)

result.index;  // 1   拿到 bc 匹配的起始地位,下标为 1
result.indices;  // [1,3]  拿到 bc 匹配的开始和完结地位的下一个字符的地位

== 十分重要!!!这里须要留神的是,匹配的字符串开始地位是字符串的第一个值,匹配的完结地位,并不在返回后果中,正确来说,匹配的完结地位是匹配字符串的开端的下一个地位,如果这一句不了解,上面的内容将会很难了解 ==

如果正则中蕴含组匹配,那么 indices 属性对应的数组就会蕴含多个成员,并且提供每个组的开始地位和完结地位

let text2 = 'abccdef'
let te2 = /bc+(de)/d
let result2 = te2.exec(text2)
console.log(result2.indices);  // [1,6][4,6] 
  • 这里的匹配并不是单个的匹配,而是整体,最外层的 bc 其实不参加独自的匹配,而是放到 bcde 整体中,所以第一个数组打印的是 1,6 b 的下标为1e 的下一个字符的下标为6`
  • 组会独自进行匹配,d的下标为 4e 的下一个字符的下标为6

多个组匹配以及下标

let text3 = 'abccdefgh'
let te3 = /bc+(de(fg))/d
let result3 = te3.exec(text3)

result3.indices;  // [1,8][4,8][6,8] 

匹配的程序:bcdefgdefgfg,对应的打印后果[1,8][4,8][6,8]

如果正则表达式蕴含具名匹配,那么 indices.groups 的属性会是一个对象,能够从该对象获取具名组匹配的开始地位和完结地位

let text4 = 'abcdefgh'
let te4 = /bc+(?<word>de)/d  // 具名组 <word>
let result4 = te4.exec(text4)

result4.indices; // [1,5][3,5]
result4.indices.groups; // {word : [3,5]}

如果未匹配上则打印 undefined


String.prototype.matchAll()

matchAll()能够一次性取出所有匹配,返回的是遍历器(Iterator)而不是数组

let strings = 'test1test2test3';
let regex = /t(e)(st(\d?))/g;

for(let match of strings.matchAll(regex)){console.log(match);  // 返回所有匹配
  }

转为数组的办法一

[...strings.matchAll(regex)]; // 返回所有匹配,后果为数组

转为数组的办法二

Array.from(strings.matchAll(regex)); // 返回所有匹配,后果为数组


案例源码:https://gitee.com/wang_fan_w/es6-science-institute

如果感觉这篇文章对你有帮忙,欢送点亮一下 star 哟

正文完
 0