思考题

今天有人问一个关于零宽断言的正则:


    1. var reg = /(?=a)b/;  
    2. reg.exec("ab");

    大家思考下这个正则为什么匹配不了?我们先了解一下什么叫零宽断言,最后再来回答这个问题。

    概念理解

    零宽断言(有的资料叫它环视),分为零宽度正预测先行断言( 格式为 (?=exp) ),和 零宽度正回顾后发断言( 格式为 (?<=exp) )。javascript目前只支持前者,所以这里我们只以前者来讨论。(为了便于表述,下文所出现的“零宽断言”或“断言”如果不特别说明,都指前者。)

    我们先从字面上来理解下。零宽度,说明它是不占字符宽度的,只是一个位置,它不匹配任何东西。但是这个“位置”跟 ^ $ \b 这三个东东是有本质区别的。还是很难理解?好吧,先略过,相信看完本文你会理解的。接下来,正预测,这个很好理解,就是表示这个表达式是一种预测,预测接下来会发生的情况。最后,先行断言,意即提前确定。结合起来,就是:我断言,在我所在的位置后面,必然要出现我所表达的东西

    说了一大堆,很多人还是云里雾里吧,那么,下面这段代码就来加深你的理解:

     

    1. var reg = /(?=abc)\w\w\w123/;  
    2. reg.exec("abc123"); // 结果 abc123  
    3. reg.exec("dbc123"); // 结果 null

    我们结合这段代码来看,我们用了一个零宽断言(?=abc),它断言了在它出现的位置接下来的三个字符必须是abc,意即我断言接下来的\w\w\w(当然,实际应用中不会写这么笨的代码,可以写成\w{3},这里只是为了便于加深理解)三个字符必须为abc。所以这里,第一个结果能够匹配abc123,而第二个结果则显示匹配不到任何东西。零宽断言就是一种预言,它告诉表达式,接下来会发生什么。

    但是问题也来了:“零宽断言不是不匹配任何东西吗?那这里第一个结果怎么匹配了abc?”  对,没错,这个表达式是匹配到了abc,但是,并不是(?=abc)匹配的呀,而是后面的\w\w\w匹配到的,我只是预测了\w\w\w将会是abc不是吗?

    应用场景

    说到这里,不少人会问了:我用 /abc123/ 不就可以匹配了吗?用你这个正则的意义何在?

    没错,这个正则把简单的问题复杂化了。但是存在的,就是有意义的。实际使用中,零宽断言(再次强调这里单指零宽度正预测先行断言)多用在匹配它之前的内容,而不是之后的内容。怎么理解呢?请看代码:

     



    1. var reg = /abc(?=123)/;  
    2. reg.exec("abc123"); // 结果 abc

    这里,我的零宽断言放在abc之后,也就是说,我断言abc之后必然要出现123。但是断言不是匹配——我只是预测将要出现的东西,我不匹配任何东西(这就是零宽度的意义)。所以,匹配结果为 abc。
    上面说的是匹配它之前的内容的情况,那么匹配它之后的内容的情况就完全没有意义了吗?No!请看下例:

     


    1. /^(?=^.{8,}$)(?=.*\d)(?=.*\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\n).*$/

    这个看起来比较复杂,是一个js群里的人发的,一个十分巧妙的正则。不过,分解开来其实一点也不复杂,这是由五个零宽断言和一个负向零宽断言(负向表否定)组成。前五个分别是:必须是8位以上、必须出现数字、必须出现特殊符号(非字母数字下划数)、必须出现大写字母、必须出现小写字母。最后一个负向零宽断言是:不得出现换行。大家会发现每个断言都是以 .* 开头,那这个表示什么意思呢?因为这些断言都是写在同一位置,而同一位置是不可能同时出现以上六种情况的。所以用 .* 来告诉表达式,这个断言之前可以有字符(意即这个断言可以出现在接下来的字符串任何位置)。那么这条正则表达式有什么作用呢?有做过密码验证的同学马上就反应过来了,这可以用来做强密码验证。

    最后的一些唠叨

    现在再回过头来看看之前的思考题,是不是一下子就明白了问题所在?(?=a) 断言了它后面必须出现a,但是紧接着的表达式却给了一个b,很显然,这个表达式本身就是不成立的。

    那么,做一下修改 /(?=a)\wb/ 不就成立了吗?可是,我之所以用零宽断言,就是想要匹配b呀,不想匹配a呀。那既然这个断言不能满足,我该怎么办呢?

    对于这种情况,那我们就不该用零宽度正预测先行断言(?=exp),而是该用零宽度正回顾后发断言(?<=exp)了。

     


    1. var reg = /(?<=a)b/;  
    2. reg.exec("ab"); // 结果 b

    不过很可惜,目前javascript是不支持这种断言的(ps.好像好多语言都不支持吧 ^-^),所以以上的代码只存在于理论中,无法在js环境中运行。