正则表达式之位置匹配

297 阅读2分钟

👩🏻‍🌾 位置匹配攻略

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

1. 什么是位置呢?

位置(锚)是相邻字符之间的位置,包括开头和结尾。 截屏2022-09-22 下午2.58.23.png

2. 如何匹配位置呢?

在es5中,共有6个锚:^$\b\B(?=p)(?!p)

还有两个es2018中的(?<=p)(?<!p)

相应的可视化形式为: 5.png

位置锚:^ 和 $

  • ^(脱字符):匹配开头,在多行匹配中匹配行开头。
  • $(美元符号):匹配结尾,在多行匹配中匹配行结尾。

示例:

把字符串的开头和结尾用#替换。(位置可以替换成字符串的!)

const result = 'hello'.replace(/^|$/g,'#')
console.log(result);
//=>"#hello#"

多行匹配模式(即有修饰符m时),二者是行的概念。

const result = "I\nlove\njavascript".replace(/^|$/m,'#')
console.log(result);
//=>
#I#
#love#
#javescript#

位置锚:\b 和 \B

  • \b:单词边界。具体就是:\w和\W之间的位置、\w和^之间的位置、\w和$之间的位置。
  • \B:非单词边界。具体就是:\w和\w、\W和\W、\W和^、\W和$

\w是字符组[0-9a-zA-Z]的简写形式,而\W是排除字符组[^0-9a-zA-Z]的简写形式。

示例:

(1)考察文件名"[JS] Lesson_01.mp4"中的\b。

const result = "[JS] Lesson_01.mp4".replace(/\b/g,"#")
console.log(result);
//=>"[#JS#] #Lesson_01#.#mp4#"

上例解析:

输出结果"[#JS#]#Lesson_01#.#mp4#"中的每一个#号:

  • 第1个:两边字符是 [ 和 J ,是\W和\w之间的位置。
  • 第2个:两边字符是 S 和 ] ,是\w和\W之间的位置。
  • 第3个:两边字符是 ] 和 L ,是\W和\w之间的位置。
  • 第4个:两边字符是 1 和 . ,是\w和\W之间的位置。
  • 第5个:两边字符是 . 和 m ,是\W和\w之间的位置。
  • 第6个:位于结尾,前面字符4是\w ,即\w和$之间的位置。

(2)文件名"[JS] Lesson_01.mp4"中的\B。

const result = "[JS] Lesson_01.mp4".replace(/\B/g,"#")
console.log(result);
//=>"#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"   // \W和^、\w和\w、\W和\W

位置锚:(?=p) 和 (?!p)

这是两个先行断言。

  • (?=p):正向先行断言。其中p是一个子表达式,即p前面的位置,或者说该位置后面的字符匹配p。
  • (?!p):负向先行断言。(?=p)的反面意思。
const result = "hello".replace(/(?=l)/g,'#');
console.log(result);
//=> "he#l#lo"

const result = "hello".replace(/(?=ll)/g,'#');
console.log(result);
//=> "he#llo"

上例中:(?=l)就表示“l”字符前面的那个位置。(?=ll)表示“ll”字符前面的位置

可视化图为:

9.png

const result = "hello".replace(/(?!l)/g,"#");
console.log(result);
//=> "#h#ell#o#"

上例中:(?!l)表示不是“l”字符前面的位置,也包括开头和结尾。

位置锚:(?<=p) 和 (?<!p)

这是两个后行断言。

  • (?<=p):正向后行断言。表示p表达式后面那个位置
  • (?<!p):负向后行断言。与(?<=p)相反,表示不能匹配p表达式后面的位置
const result = "hello".replace(/(?<=l)/g,"#");
console.log(result);
//=> "hel#l#o"

const result = "hello".replace(/(?<!l)/g,"#");
console.log(result);
//=> "#h#e#llo#"

其中/(?<=l)/表示l后面的那个位置,/(?<!l)/表示不是l后面的位置。

es5 就支持了先行断言。 es2018才支持后行断言

有些资料没有很好的强调(?=p)、(?!p)、(?<=p)和(?<!p)这四者就是个位置。比如(?=p),就与开头^一样,就是p前面那个位置。

零宽断言:

零宽:只匹配位置,在匹配过程中,不占用字符,所以被称为零宽。

(?=p)、(?!p)、(?<=p)和(?<!p)统称为零宽断言,也可以称之为环视,即左看看右看看。

3. 位置的特性

对于位置的理解,可以理解成空字符串“”。字符之间的位置可以写成多个。

位置理解成空字符,是对位置非常有效的理解方式。

比如"hello"字符串等价于如下的形式:

"hello" == ""+"h"+""+"e"+""+"l"+""+"l"+""+"o"+"";
"hello" == ""+""+"hello";

因此:/^hello$/ == /^^hello$$$/

const result = /^^hello$$$/.test("hello");
console.log(result);
//=>true

更复杂的例子:

const result = /(?=he)^^he(?=\w)llo$\b\b$/.test("hello");
console.log(result);
//=>true

4. 案例

不匹配任何东西的正则

/.^/ : 要求.字符后面是字符串开头,而这样的字符串是不存在的。

数据的千位分隔符表示法

  • 题1:将“12345678”,变成“12,345,678”。

实现:将相应的位置替换成“,”。

const result = "12345678".replace(/(?!^)(?=(\d{3})+$)/g,',')
//=>"12,345,678"

其中(?!^)表示匹配的位置不能是开头。(?=(\d{3})+$)表示从结尾向前数,一旦是3的倍数,就把其前面的位置替换成逗号。

如果不加(?!^),则"123456789"会处理成",123,456,789",所以要把开头排除掉。

  • 题2:把 "12345678 123456789" 替换成 "12,345,678 123,456,789"

这个应该是从结尾和数字之间位置、空格和数字之间的位置开始往前数,即\b。 不能在开头、空格和数字之间的位置,即\b。实现如下:

const result = "12345678 123456789".replace(/(?!\b)(?=(\d{3})+\b)/g,',')
//=>"12,345,678 123,456,789"

其中(?!\b)其实就是\B,所以最终的正则是:/\B(?=(\d{3})+\b)/g

其可视化图为:

10.png

验证密码问题

题:密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符。

解法1(推荐):

“至少包含2种字符”,那么不能全部都是数字,也不能全部都是小写字母,也不能全部都是大写字母。

const regex = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9a-zA-Z]{6,12}$/g;
console.log(regex.test('1234567')) // false,全是数字
console.log(regex.test('aaaaaa')) // false,全是小写字母
console.log(regex.test('AAAAAA')) // false,全是大写字母
console.log(regex.test('aaAA1')) // false,不是最小6位

其中:

  • (?!^[0-9]{6,12}$):表示不能全部是数字。
  • (?!^[a-z]{6,12}$):表示不能全部是小写字母。
  • (?!^[A-Z]{6,12}$):表示不能全部是大写字母。 其可视化形式为:

6.png

解法2:

同时包含两种字符的情况有:

  • 同时包含数字和小写字母
  • 同时包含数字和大写字母
  • 同时包含小写字母和大写字母
const regex = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9a-zA-Z]{6,12}$/g;

console.log( regex.test("1234567") ); // false 全是数字
console.log( regex.test("abcdef") ); // false 全是小写字母
console.log( regex.test("ABCDEFGH") ); // false 全是大写字母
console.log( regex.test("ab23C") ); // false 不足6位
console.log( regex.test("ABCDEF234") ); // true 大写字母和数字
console.log( regex.test("abcdEF234") ); // true 三者都有

其中: (?=.[0-9])表示必须包含数字,(?=.[0-9])(?=.*[a-z])表示必须包含数字和字母,其他同理。

其可视化过程为:

7.png

正则可视化图网站