曾自己借助阿里云和hexo搭了个站点,现已废弃,过往写的博客暂挪到此处。
title: JS正则表达式之先行零宽断言
subtitle: 只匹配,不返回,不消费
date: 2016-12-13 17:23:03
tags:
- 技术
- Javascript
- 正则
- TODO
点击这里可以直接看我的解析,跳过啰嗦的自我探索过程
JS的正则表达式,关于零宽断言,只有先行断言。
什么叫先行断言呢?
JS犀牛书 里是这样介绍的
字符 | 含义 |
---|---|
(?=p) | 零宽正向先行断言,要求接下来的字符都与p匹配,但不能包括匹配p的那些字符 |
(?!p) | 零宽负向先行断言,要求接下来的字符不与p匹配 |
例子如下:
var str1 = "bedroom";
var str2 = "bedding";
var reBed = /(bed(?=room))///在我们捕获bed这个字符串时,抢先去看接下来的字符串是不是room
alert(reBed.test(str1));//true
alert(reBed.test(str2))//false
var str1 = "bedroom";
var str2 = "bedding";
var reBed = /(bed(?!room))/ //要来它后面不能是room
alert(reBed.test(str1))//false
alert(reBed.test(str2))//true
看起来很简单是不?
如果你想做一个匹配,字符串 http://www.baidu.com?from=monvhh=endbalabla,我要得到 ?from=monvhh=end
中的monvhh,当然这个monvhh可能是任意的字符串
想通过一次正则匹配就得到?from=
和=end
中间的字符串,正则如此强大,一定可以。
那么我需要去匹配?from=
和=end
,并且我还不要它们。只有零宽断言可以做到了!
var reg = /(?=\?from=)\w+(?=end)/
好简单,好方便是不是!
然而失败了。。。
WHY?!
谷歌良久,得到的结果就是
- 1)先行断言嘛,只能匹配结尾(网上的例子都是匹配结尾!会去匹配前置的,都是零宽负向先行断言(?!p))。
- 2)后行断言可以实现我这个思路。
- 3)零宽断言只占位,不消费。
只能匹配结尾,为什么?根据它的定义,不至于啊。
是因为“都”字的原因么?
但是
var reg = /Java(?=Script)/;
var str1 = 'JavaScript';
var str2 = 'JavaScripter';
reg.test(str1);//true
reg.test(str2);//true
str2的结尾除了Script
还有er啊,并不是结尾完全匹配Script
,那我在匹配这个之后再继续匹配别的,怎么就不行了呢?
3)解释了这个问题。但我当时一直没想通,跟我这个有什么关系,直到做了大量的测试。。。
var reg = /(?=\?from=)\w+(?=end)/
这个例子,我一直以为是(?=\?from=)
这部分用错了,是先行零宽断言的错。
确实是它的错,但并不是在解析到它这里出错的。
而是在它之后的\w+
,如果换成\S
就能test成功了。(仅仅是test成功了)
为什么?monvhh
符合\w
啊。
原因是:零宽断言只占位,不消费。即
匹配完(?=\?from=)
之后的字符串是?from=monvhh=endbalabla
;
注意:此字符串的意思是:待匹配字符串。
如果你拿(?=\?from=)
单独去test字符串http://www.baidu.com?from=monvhh=endbalabla
,是true,如果用该字符串去match该正则,只能得到一个空字符串,对,还不是null。
因为零宽断言就是只匹配,但并不得到这个匹配的结果。这就是我为什么用它的原因嘛,我需要判断字符串里有你,但是我不要你。
也就是只占位,不消费。它验证过,但是并没有把它消费掉。
所以,此时继续匹配\w+
就会报错,匹配\S+
却可以,应为此时的字符串里还有?和=。
这就是“先行断言”这个词的关键了。
它就像一个if语句,它只判断,在我当前位置之后,有某个字符串。但并不改变这个字符串,也不对结果有任何影响。
而其他正则匹配,是一个处理原始字符串的过程(不改变这个字符串,但是改变匹配之后的结果result)。
这是我理解的,正则匹配就像是一个匹配字符串返回result的结果,这个result在一开始等于这个字符串,在匹配的过程中从左至右削减:匹配正则中第一个位置成功之前的,削掉;然后在目前的result中,就继续匹配正则中下一个位置;若没匹配上,就把目前的result中削减至result中当前正在匹配的位置(即之前匹配该正则前部分成功的那些字符串也抛弃掉),在剩下的result中继续匹配。
(以上理解仅为只匹配一次的情况,分组和全局模式应当是在此基础上组合)
如果削减到最后都没有匹配上这个正则,那么test的话就是false了。
而断言却不同,它匹配上,却不削减。
它只做了if这一步,其他什么都没做(相较于以上处理result的过程,它对result不作任何处理)。
在我之前的理解中,只知道它匹配上,却不返回。
对,它既不返回,也不削减。
如果先行断言用在正则的最后,那么它不返回。
如果先行断言,并不是在正则的尾部,那么,虽然它不返回了,但是因为剩下部分正则还在继续匹配,它没有削减result,所以接下来的正则,还得继续匹配该先行断言明明已经匹配上的部分。相当于只卡了个关口(if语句),嘛都没做。当然这就是它的魅力所在,我是说定义。。
这是正向先行断言。
负向先行断言的话,就没有这个削减的问题,因为它本来就被期待匹配不上,当然不会削减。但用它的时候也要小心,毕竟跟[^...]
的功能不一样,因为[^...]
虽然是期待匹配不上,但是它却占了一个位置,在它之后部分的正则,从它这占了一个位置的字符串之后开始匹配。
<span id="analysis">用我的方式理解正则匹配(包括零宽断言)</span>:
只是简单的匹配逻辑,不考虑什么分组、匹配位置、修饰符等;
var reg,str;
//正则reg,待匹配字符串str;
//reg是一串针对连续位置的匹配规则,我在强调连续两个字,断言也不能;
//str是一串连续的字符串;
var reg_pointer = 0,str_pointer = 0;
//指针reg_pointer;指针str_pointer;
//分别为正则reg,待匹配字符串str的指针,初始为0,即首位;
var str_length = str.length,reg_length = reg.length;
//此处reg.length仅表示正则reg的占位,先把零宽断言也算,方便对比;
var result;
//result = str.substring(str_pointer,str_length);所以str_pointer可以表达我说的削减的意思;
//了解正则表达式中的贪婪、惰性、支配性之后,知道我所说的削减,其实就是贪婪性匹配的特质!!
for( str_pointer < str.length && reg_pointer < reg_length ){
var bool = str[str_pointer]匹配reg[reg_pointer];
//reg[reg_pointer]仅表示正则中的第reg_pointer的占位,先把零宽断言也算,方便对比;
if( reg[reg_pointer] != 先行零宽断言 && bool === false ){
//重新再来,该轮匹配失败,从接下来的字符串再继续匹配整条正则
reg_pointer=0;
str_pointer++;
//与先行零宽断言的一致,匹配失败都是一样的
}
if( reg[reg_pointer] != 先行零宽断言 && bool === true ){
//继续匹配,削减
reg_pointer++;
str_pointer++;
}
if( reg[reg_pointer] == 先行零宽断言 && bool === false ){
//重新再来,该轮匹配失败,从接下来的字符串再继续匹配整条正则
reg_pointer=0;
str_pointer++;
}
if( reg[reg_pointer] == 先行零宽断言 && bool === true ){
//继续匹配,但不削减
reg_pointer++;
//所以字符串的指针不变,但是正则进行到下一个占位了。
//这就是零宽断言不占位的真谛了。
}
result = str.substring(str_pointer,str_length);
}
//匹配失败
//没有reg_length-1是因为如果最后一次还是匹配成功,还会再做一次reg_pointer++;
if( reg_pointer < reg_length ){
result = undefined;
}
return result;
结果
所以它实现不了我的想法。
最后我通过分组的方式,然后match数组中的第一个分组得到的结果。如下:
var reg = /\?from=(\w+)=end/;
var result = 'http://www.baidu.com?from=monvhh=endbalabla'.match(reg);
var whatiwant = result[1];//monvhh
TODO 如何在正则中达到跳过的效果。
意即,破坏我在解析正则匹配实现过程中指出的,
reg是一串针对连续位置的匹配规则,我在强调连续两个字,断言也不能;
比如,我想跳过几个字符,再继续匹配,或者我想跳过某几个特定的字符,再继续匹配。怎么搞?
我是说一次性的,不要跟我说分组之后继续。。。或者substring。。。
如果是跳过前面和后面的,可以用后行断言匹配前面,匹配成功但不返回,先行断言匹配后面的,匹配成功但不返回。成功实现要匹配但返回结果却跳过的需求。
然后如果要跳过的是中间的呢?
本文参考
http://www.cnblogs.com/rubylouvre/archive/2010/03/09/1681222.html
http://fxck.it/post/50558232873
网友评论