请把我忽略 發表於 2024-3-16 11:34:20

正则表达式中?=、?!、?<=、?<!、?:的理解与应用举例

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、?=、?!、?<=、?<!、?:的解释</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">1. 先看一下比较官方的解释</a></li><li><a href="#_lab2_0_1">2. 再看一下比较通俗易懂的解释:</a></li><li><a href="#_lab2_0_2">3. 零宽度断言</a></li><li><a href="#_lab2_0_3">4. ?: 的解释</a></li></ul><li><a href="#_label1">二、举例</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_4">?=</a></li><li><a href="#_lab2_1_5">?!</a></li><li><a href="#_lab2_1_6">?&lt;=</a></li><li><a href="#_lab2_1_7">?&lt;</a></li><li><a href="#_lab2_1_8">?:</a></li></ul><li><a href="#_label2">三、特殊情况</a></li><ul class="second_class_ul"></ul><li><a href="#_label3">四、实例应用</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">&ldquo;?&rdquo;的几种用法</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">总结&nbsp;</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一、?=、?!、?<=、?<!、?:的解释</h2>
<p class="maodian"><a name="_lab2_0_0"></a></p><h3>1. 先看一下比较官方的解释</h3>
<ul><li><code>(?=pattern)</code>:正向先行断言,表示匹配位置后面必须紧跟着满足&nbsp;<code>pattern</code>&nbsp;的字符串,但不包括这个字符串在匹配结果中。</li><li><code>(?!pattern)</code>:负向先行断言,表示匹配位置后面不能紧跟着满足&nbsp;<code>pattern</code>&nbsp;的字符串,也不包括这个字符串在匹配结果中。</li><li><code>(?&lt;=pattern)</code>:正向后行断言,表示匹配位置前面必须是满足&nbsp;<code>pattern</code>&nbsp;的字符串,但不包括这个字符串在匹配结果中。</li><li><code>(?&lt;!pattern)</code>:负向后行断言,表示匹配位置前面不能是满足&nbsp;<code>pattern</code>&nbsp;的字符串,也不包括这个字符串在匹配结果中。</li><li><code>(?:pattern)</code>:非捕获型分组,表示将&nbsp;<code>pattern</code>&nbsp;包含在一个分组中,但不把这个分组的匹配结果保存到分组编号中。这个分组通常用于表示可选的或重复的子表达式,或者是限制量词的作用范围,而不需要把它们的匹配结果单独提取出来。</li></ul>
<p class="maodian"><a name="_lab2_0_1"></a></p><h3>2. 再看一下比较通俗易懂的解释:</h3>
<ul><li>RegExp1(?=RegExp2)&nbsp; 匹配后面是RegExp2&nbsp;的&nbsp;RegExp1</li><li>RegExp1(?!RegExp2)&nbsp; 匹配后面不是RegExp2&nbsp;的&nbsp;RegExp1</li><li>(?&lt;=RegExp2)RegExp1&nbsp; 匹配前面是RegExp2&nbsp;的&nbsp;RegExp1</li><li>(?&lt;!RegExp2)RegExp1&nbsp; 匹配前面不是RegExp2&nbsp;的&nbsp;RegExp1</li><li>(?:RegExp)&nbsp; 这个等下单独解释,与上面的不太一样</li></ul>
<p>是不是有点明白了,其实?=、?!、?<=、?<!的意思可以理解为&nbsp;if 判断,即只有先通过它们(RegExp2)的判断之后,才可以获取到正则(RegExp1)的匹配结果。</p>
<p class="maodian"><a name="_lab2_0_2"></a></p><h3>3. 零宽度断言</h3>
<p>?=、?!、?<=、?<!其实就是正则表达式中的<strong>零宽度断言</strong>,以上面的举例来解释&darr;</p>
<p>&nbsp;RegExp2匹配到的内容是不会返回的,也不会消耗匹配到的字符,只会返回RegExp1的匹配结果,这就是零宽度断言,零宽度断言在正则表达式中非常有用,因为它们可以在不改变匹配结果的情况下,对匹配位置前后的内容进行限制或判断。</p>
<p class="maodian"><a name="_lab2_0_3"></a></p><h3>4. ?: 的解释</h3>
<p>(?:) 并不是零宽度断言,而是非捕获组,它跟普通的括号 () 的区别在于,它不会保存匹配到的内容,但是它仍然会消耗字符并返回匹配内容,只是不会保存匹配结果。</p>
<ul><li>()表示捕获分组,它会把匹配到的内容保存到内存中,开发者可以使用$n(n是一个数字)来代表第n个()中匹配到的内容</li><li>(?:)表示非捕获组,它匹配的内容不会被保存,所以无法使用$n获取,但也因为没有被保存所以节省了一部分内存空间</li></ul>
<p class="maodian"><a name="_label1"></a></p><h2>二、举例</h2>
<p class="maodian"><a name="_lab2_1_4"></a></p><h3>?=</h3>
<div class="jb51code"><pre class="brush:js;">'我喜欢苹果'.replace(/我喜欢(?=苹果)/,'我讨厌') // 匹配 我喜欢苹果 中的 我喜欢 并替换为 我讨厌,因为是零宽度断言所以不包含苹果,故结果为 我讨厌苹果
'我喜欢橘子'.replace(/我喜欢(?=苹果)/,'我讨厌') // 我喜欢后面不是苹果,所以这里正则未通过,匹配不到任何内容,故结果仍为 我喜欢橘子</pre></div>
<p class="maodian"><a name="_lab2_1_5"></a></p><h3>?!</h3>
<div class="jb51code"><pre class="brush:js;">'我喜欢苹果'.replace(/我喜欢(?!苹果)/,'我讨厌') // 匹配后面不是苹果的我喜欢,正则未通过,故结果仍为 我喜欢苹果
'我喜欢橘子'.replace(/我喜欢(?!苹果)/,'我讨厌') // 正则通过,匹配到 我喜欢 进行替换,因为是零宽度断言所以橘子不在匹配结果中,故结果为 我讨厌橘子</pre></div>
<p class="maodian"><a name="_lab2_1_6"></a></p><h3>?&lt;=</h3>
<div class="jb51code"><pre class="brush:js;">'我喜欢苹果'.replace(/(?&lt;=我喜欢)苹果/,'西红柿') // 匹配到 苹果 ,故结果为 我喜欢西红柿
'我喜欢橘子'.replace(/(?&lt;=我喜欢)苹果/,'西红柿') // 匹配不通过,故结果仍为 我喜欢橘子</pre></div>
<p class="maodian"><a name="_lab2_1_7"></a></p><h3>?&lt;</h3>
<div class="jb51code"><pre class="brush:js;">'我讨厌苹果'.replace(/(?&lt;!我喜欢)苹果/,'西红柿') // 匹配到 苹果 ,故结果为 我讨厌西红柿
'我喜欢苹果'.replace(/(?&lt;!我喜欢)苹果/,'西红柿') // 匹配不通过,故结果仍为 我喜欢苹果</pre></div>
<p class="maodian"><a name="_lab2_1_8"></a></p><h3>?:</h3>
<div class="jb51code"><pre class="brush:js;">'hello world'.replace(/(?:hello) (world)/,'$1') // 匹配内容为hello world,但是hello并没有被保存,因此$1取的是world,故结果为world</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>三、特殊情况</h2>
<p>正则平时我们很少会自己写,一般都是复制别人的~~~(别人的才是最好的)。然后就经常看到一种写法,比如:</p>
<blockquote><p>/(?=.*){5,10}/</p></blockquote>
<p>这时候可能有些人就想,咦,(?=)不都是符合条件后匹配它前面的内容吗?这里为什么能放在开头 呢,他前面没内容啊?其实大家可以这么理解,当(?=)前面没有内容,或者说(?=)被放在正则开头使用时,(?=)的作用就相当于检索全部内容是否符合它的要求,如果不符合也就没必要继续向后匹配了,这就很像if判断,只有当条件为true时,才能执行后面的内容。</p>
<p>所以这里的正则意为:先检查内容中是否至少包含一个大写字母,如果有,则继续检查并匹配5~10个大小写字母,将这5~10个大小写字母作为结果返回。</p>
<p class="maodian"><a name="_label3"></a></p><h2>四、实例应用</h2>
<p>姓名脱敏(添加*号)</p>
<div class="jb51code"><pre class="brush:js;">'李小龙'.replace(/(?&lt;=[\u4e00-\u9fa5])[\u4e00-\u9fa5]/g, '*') // 李**</pre></div>
<p>手机号/银行账号脱敏</p>
<div class="jb51code"><pre class="brush:js;">'13912345678'.replace(/(?&lt;=\d{3})\d(?=\d{3})/g, '*') // 139*****678</pre></div>
<p>强密码规则校验</p>
<div class="jb51code"><pre class="brush:js;">// 密码不能为空,8-30位,至少包含一个大写字母、小写字母、数字、特殊字符
/^(?=.*)(?=.*)(?=.*)(?=.*[\W_])(?!.*[\u4e00-\u9fa5])(?!\s){8,30}$/</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>&ldquo;?&rdquo;的几种用法</h2>
<ul><li>&ldquo;?&rdquo;元字符规定其前导对象必须在目标对象中连续出现零次或一次。</li><li>当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串&ldquo;oooo&rdquo;,&ldquo;o+?&rdquo;将匹配单个&ldquo;o&rdquo;,而&ldquo;o+&rdquo;将匹配所有&ldquo;o&rdquo;。</li><li>(?:pattern) &mdash;&mdash;匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符&ldquo;(|)&rdquo;来组合一个模式的各个部分是很有用。例如&ldquo;industr(?:y|ies)&rdquo;就是一个比&ldquo;industry|industries&rdquo;更简略的表达式。</li><li>(?=pattern)&mdash;&mdash;正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,&ldquo;Windows(?=95|98|NT|2000)&rdquo;能匹配&ldquo;Windows2000&rdquo;中的&ldquo;Windows&rdquo;,但不能匹配&ldquo;Windows3.1&rdquo;中的&ldquo;Windows&rdquo;。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。</li><li>(?!pattern)&mdash;&mdash;正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如&ldquo;Windows(?!95|98|NT|2000)&rdquo;能匹配&ldquo;Windows3.1&rdquo;中的&ldquo;Windows&rdquo;,但不能匹配&ldquo;Windows2000&rdquo;中的&ldquo;Windows&rdquo;。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始</li><li>(?&lt;=pattern)&mdash;&mdash;反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,&ldquo;(?&lt;=95|98|NT|2000)Windows&rdquo;能匹配&ldquo;2000Windows&rdquo;中的&ldquo;Windows&rdquo;,但不能匹配&ldquo;3.1Windows&rdquo;中的&ldquo;Windows&rdquo;。</li><li>(?&lt;!pattern)&mdash;&mdash;反向否定预查,与正向否定预查类拟,只是方向相反。例如&ldquo;(?&lt;!95|98|NT|2000)Windows&rdquo;能匹配&ldquo;3.1Windows&rdquo;中的&ldquo;Windows&rdquo;,但不能匹配&ldquo;2000Windows&rdquo;中的&ldquo;Windows&rdquo;。</li><li>(?i)&mdash;&mdash;该表达式右边的字符忽略大小写</li><li>(?-i)&mdash;&mdash;该表达式右边的字符区分大小写</li><li>(?i:x)&mdash;&mdash;x 忽略大小写</li><li>(?-i:x)&mdash;&mdash;x 区分大小写</li><li>?和懒惰匹配&mdash;&mdash;尽可能少的匹配,例如:源字符串str=&ldquo;dxxddxxd&rdquo;中,d\w*?会匹配 dx,而d\w*?d会匹配 dxxd。</li></ul>
<p class="maodian"><a name="_label5"></a></p><h2>总结&nbsp;</h2>
頁: [1]
查看完整版本: 正则表达式中?=、?!、?<=、?<!、?:的理解与应用举例