我们想象一下这样的场景:需要用正则表达式去标记要匹配的文本位置,而不仅仅是去查找文本本身。这就需要我们正则中的另一个知识:前后查找。
从需求中去理解"前后查找"
所谓的前后查找,在进步一用大白话来说就是:我要查找某个位置的前面(或后面)的内容。比如:
https://baidu.com
,我们需要查找:
前面的,也就是https
这几个字。
或许对于上面这个例子,不用前后查找也能实现。比如用"文本匹配"、子表达式以及利用开始^
、结束$
符都可以。但是很显然是比较耗时、繁琐的。比如下面这个例子:
<body>
<h1>我是H1标签</h1>
</boy>
对于上面的例子中,我们想要获取到标题标签里的内容, 注意:仅仅是要获取内容,而不获取标签本身。
或许我们可以这么写:
<h1>.*<\/h1>
或许再完善一些,可以这么写:
<[hH]([1-6])>.*<\/[hH]\1>
我们会发现,这样虽然可以获取到标题标签和内容,但是我们不想要<h1>——<h6>
这些元素标签,我们只需要标签里的内容。虽然我们可以在进一步对返回的匹配结果进行处理,但是相对于前后查找显然是比较繁琐的。
有了实际场景需求后,可以去尝试用前后查找的方法去解决。
前后查找实际应用
前后前后,肯定会有前有后,也就是会有向前查找和向后查找。
向前查找
基本语法:?=
先来看几个例子
- 找出URL中的使用的是什么协议。
let str1 = 'https://baidu.com';
let str2 = 'http://qq.com';
let str3 = 'ftp://taobao.com';
// 正则表达式
let regexp = /.+(?=:)/;
str1.match(regexp); // https
str2.match(regexp); // http
str3.match(regexp); // ftp
这里,我们在js中使用match
方法去找出协议名称,可以很容易的匹配到。
对于这段正则/.+(?=:)
, 其中?=
正是向前查找的语法。对于使用()
包裹起来的是字表达时,这个我们应该了解,也就是说,在子表达式中, 我们通过向前查找的语法?=
告诉正则, 我们只匹配:
前面的内容。
用一些专业术语来说, 我们这里
?=:
的意思是:只要找到:
就行,但是不消费它,并且返回:
前面的内容。
如果我们不用向后查找?=
试一下:
正则:.+:
使用上面的正则运行一下你会发现,冒号:
也被包含在匹配结果里了,显然,这不是我们想要的。
向后查找
基本语法: ?<=
我们发现,向后查找相较于向前查找,在语法上,知识少了一个箭头:
<
。在概念上,理解了前面的向前查找,向后查应该也会理解。
所谓的向后查找,就是查找某个字符位置的后面内容。
先来看几个例子
还是拿前面的向前查找的的匹配URL协议的例子。只不过我们现在把需求改一下,改成获取URL地址,但是不包含协议头和冒号。
修改后的正则: (?<=:).+
。
这样,我们就会获取到协议后面的网址。也就是说会获取:
后面的内容(虽然带上了两个斜线//
)。
当然,这里我们可以直接调用JS中的location对象中的host(或者hostname)属性,包括协议名称也可以拿到,但是我们这里说的是正则😁。
解决问题(前后查找同时用)
前面通过两个简单的例子,我们理解了什么是向后查找和向前查找。前后查找就是匹配一个标识的前后内容,可以将这个标识理解成界定符(或分隔符), 但是不消费这个界定符。
理解了之后,我们开始着手解决刚开始我们遇到的场景:去找出标题标签中的内容。
在开始之前,我们要知道,前后查找可以同时用,也就是说向后查找和向前查找可以同时用。
而这里我们要查找出来标签里的文本内容,换句话说,就是要查找<h1>
和</h1>
之间的内容。
再进一步理解,就是我们要对<h1>
进行向后查找,对<h1/>
进行向前查找。
文本:
<body>
<h1>我是H1</h1>
</body>
正则:(?<=<h1>).*(?=<\/h1>)
。
这里我们通过两个子表达式来解决了前面的问题,两个子表达式就像两个定界符,分别在两边卡住了我们需要的内容。解决思路前面我们也已经解释了,就是前后查找同时用。
当然,这个正则有一些局限性,只能匹配<h1>
标签,我们可以进一步完善:
完善后的正则:
(?<=[hH]([1-6])>).*(?=<\/[hH]\1>)
这里我们可以成功匹配<h1>
到<h6>
标签之间的内容,并且不论标签是否大小写,还有一点就是:利用了回溯引用,前后标签不匹配的我们会忽略。关于回溯引用,我的另外一篇文章中有写到。
负向前(后)查找
这里所说的负向前查找和负向后查找,和前面我们所说的向前查找和向后查找是相对的。通俗点说,这里的负就是取非的意思,也就是对前后查找取非。
这里的取非,和我们正则中的另外一个取非字符^
不同,这里的取非,并没有使用^
字符。
负向前查找
负向前查找会向前查找与给定模式不匹配的内容。
基本语法: ?!
我们来看一个例子
123(?!com) //匹配后面不带com的123
可以匹配: 123cn
不能匹配:123com
负向后查找
负向后查找会向后查找与给定模式不匹配的内容
基本语法:?<!
(?<!com)123 // 匹配前面不带com的123
可以匹配: abc123
不可匹配: com123
我们来看一个例子
文本:
I paid $30 for 100 apples,
50 pranges, and 60 pears,
I saved $5 on this order.
我们需要做的是:找出价格,但是不包含$
符号。这里我们使用向后查找。
正则:(?<=\$)\d+
这样,我们会查找出价格,在这里就是30
和5
如果我们要查找出来数量,这里就要使用到负向后查找。
正则:\b(?<!\$)\d+
我们此时会返回100
、50
和60
。
另外我们这里使用\b
元字符来定义了单词边界,是为了防止出现查找到的内容包含一些非单词的内容。你可以尝试下去掉\b
元字符再去匹配下,会发现返回的结果中包含了$30
中的0,因为这个0完全符合规则,因为0前面不是$。
当然了,如果不使用负向前查找,也可以使用
[^$]\d+
,但是需要注意的是,返回的每个结果前面会包含一个空格。
总结
这里的前后查找又称零宽断言。
有四种基本的前后查找操作符:
-
?=exp
: 向前查找, 找出exp表达式位置前面的内容 -
?!exp
: 负向前查找,找出内容后面不等于exp表达式的内容 -
?<=exp
: 向后查找,找出exp表达式位置后面的内容 -
?<!exp
: 负向后查找,找出内容前面不等于exp的内容
所谓的前后查找,就是将我们充当定界符的东西放在=
后面,这样就不会消费这个定界符,返回的结果只是这个界定符前面(或后面)的内容。
同时,我们可以将前后查找组合使用。
网友评论