正则中的一些高级查找

在正则中,拥有所谓的先行断言,后行断言,正向否定查找,反向否定查找等等使用方式,听起来令人头秃,他们分别表示什么含义?

首先,MDN 如是说明:

字符含义
(?:x) 匹配x但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。
x(?=y)匹配x仅仅当x后面跟着y.这种叫做先行断言
(?<=y)x匹配x仅当x前面是y.这种叫做后行断言。
x(?!y)仅仅当x后面不跟着y时匹配x,这被称为正向否定查找。
(?<!y)x仅仅当x前面不是y时匹配x,这被称为反向否定查找。

仅看概念确实不太容易记住,下面举几个栗子了解一下它们每一种方式的使用场景,加深记忆。

非捕获分组?:

说到非捕获分组,肯定还有捕获分组。

捕获分组很简单,使用一个()表示,在()中的表示一个分组,使用()会把每个分组里面的值保存起来,之后使用$1$2等等这样的方式去引用不同分组。

但有的时候虽然使用括号了,但是不想让其捕获为分组怎么办,那就可以用到?:非捕获分组。使用(?:exp)表示这个分组是不需要被捕获的,所以里面的值也不会得到保存,你也无法引用。

举个栗子,假如有一个电话号码021-1234567,如果我想匹配-之后的数字,正则可以这样写。

'021-1234567'.replace(/\d{3}-(\d{7})/, '$1'); // 1234567

这里\d{7}表示第一个分组,所以后面可以用$1来直接匹配。假如我强行给\d{3}加上分组,那么\d{3}就成了第一个分组,而\d{7}会变成第二个分组。

'021-1234567'.replace(/(\d{3})-(\d{7})/, '$2'); // 1234567

我们这个示例比较简单,可以明确知道\d{7}是第二个分组,但是,有的时候某些分组并没有什么意义,况且,在复杂情况下,分组区分起来也会比较麻烦。如果还想让\d{7}为第一分组,使用?:来让其不参与捕获就可以了。

'021-1234567'.replace(/(?:\d{3})-(\d{7})/, '$1'); // 1234567

先行断言?=

语法

x(?=y)

即查找y前面的x

如下栗子,查找hello worldworld前面的hello,找到的话,将hello替换成whole

'hello world'.replace(/hello(?=\sworld)/, 'whole'); // whole world
'hello1 world'.replace(/hello(?=\sworld)/, 'whole'); // 输出:hello1 world,因为 world前不是hello,所以无法匹配到

比如千分位的转换,也可以使用到先行断言。

'123456789.2456'.replace(/(\d)(?=(\d{3})+\.)/g, '$1,');

它的匹配方式如下图所示。

匹配逻辑

或者这样写也是可以的。

'1212123456789.1231'.replace(/\B(?=(\d{3})+\.)/g, ',');

首先\B表示非单词边界,所以,这个正则的意思是匹配3n*数字 + 小数点组合前面的非单词边界。

后行断言?<=

语法

(?<=y)x

即查找y后面的x

还使用上面的示例,如果将hello world替换为hello there,使用后行断言,如下所示。

'hello world'.replace(/(?<=hello\s)world/, 'there'); // hello there

另一个例子

const reg = /(?<=\$)foo/g;
'$foo %foo foo'.replace(reg, 'bar'); // '$bar %foo foo'

示例中将$foo全部替换成了$bar。那么,如果不使用后行断言的话,是下面这样。

const reg = /(\$)foo/g;
'$foo %foo foo'.replace(reg, '$1bar'); // '$bar %foo foo'

嗯,还是后行断言看起来更加优雅一点。

正向否定查找?!

语法

x(?!y)

即当x后面不是y时,匹配x

'hello world, hello everyone, world hello'.replace(/hello(?!\sworld)/g, 'hi'); // hello world, hi everyone, world hi

这里查找hello后面不是worldhello,那么除了第一个hello world都符合。

反向否定查找?<!

语法

(?<!y)x

即当x前面不是y时,匹配x

'hello world, hi world, whole world'.replace(/(?<!hello\s)world/g, 'world2'); // hello world, hi world2, whole world2

查找world前不是hello的字符串,符合条件时,将world替换成world2

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的电子邮箱地址不会被公开。