正则表达式中,有一个绕不过去的坎,那就是零宽断言
css
零宽断言
是一种零宽度的匹配,它匹配的内容不会保存到匹配结果中,也不会占用index
宽度,最终匹配的结果只是一个位置html
简单的说,它用于查找在某些内容以前或以后的东西(但返回结果并不包括这些内容)正则表达式
JavaScript
中只支持零宽先行断言
工具
零宽断言
分为4
类学习
正向零宽先行断言(?=exp
)测试
exp
这个表达式负向零宽先行断言(?!exp
).net
exp
这个表达式正向零宽后发断言(?<=exp
)code
exp
这个表达式负向零宽后发断言(?<!exp
)regexp
exp
这个表达式注,关于先行
和后发
,还有其它称呼,譬如前瞻
和后瞻
等,本文统一使用先行
与后发
htm
JavaScript
中的断言JavaScript
语言内只支持零宽先行断言
(即只支持?=exp
和?!exp
)
因此本文中只会介绍零宽先行断言
另外,能够经过RegexBuddy 4
等工具分析正则的匹配过程
示例1
var str="abcdefg"; var reg=/ab(?=cd)/; str.match(reg); // ["ab", index: 0, input: "abcdefg"]
index = 0
,a
匹配a
成功,尝试b
匹配b
成功(?=cd)
接管控制权(?=cd)
依次尝试匹配c
和d
成功示例2
var str="abcdefg"; var reg=/(?=cd)efg/; str.match(reg); // null
想要达到的效果是匹配在cd
后方的efg
,可是这是零宽后发断言
才有的效果(?<=exp
),而JS
中并不支持,此时使用先行断言,实际效果为
?=cd
获取控制权,一直到index = 2
时才匹配成功,接下来e
获取控制权?=cd
是零宽式的,所以匹配成功后,下一轮匹配依然从index = 2
开始尝试,此时c
匹配e
失败,因而index
挪到3
d, e, f, g
匹配?=cd
失败,因而最终匹配失败,返回null
示例3
var str="abcdefg"; var reg=/(?=cd)cdefg/; str.match(reg); // ["cdefg", index: 2, input: "abcdefg"]
基于示例2的变形
index = 2
时,?=cd
匹配成功了,交给cdefg
index = 2
,此时恰好c
匹配,继续吃进d
,e
,f
,g
也都匹配,因而匹配成功,因而返回成功结果示例4
var str="abcdefg"; var reg=/ab(?=cd)cdefg/; str.match(reg); // ["abcdefg", index: 0, input: "abcdefg"]
上述示例的综合
index = 0
时,左侧的ab
匹配成功index = 2
处),?=cd
也匹配成功index = 2
开始尝试,c, d, e ,f , g
依次匹配成功,因而匹配结束,返回成功结果(index = 0
,由于没有失败,后续的尝试都成功了)注,零宽断言返回的是位置而不是字符,零宽断言匹配成功后,其他表达式会基于这个返回的位置继续判断
另外,请不要把先行断言
当成后发断言
来用
示例1
var str="abcdefg"; var reg=/ab(?!cd)/; str.match(reg); // null
ab
匹配成功后,接下来cd
匹配?!cd
失败b, c, d, e, f, g
依次都匹配a
失败,因而最终匹配失败,返回null
示例2
var str="abcdefg"; var reg=/ab(?!ab)cd/; str.match(reg); // ["abcd", index: 0, input: "abcdefg"]
ab
匹配成功后,接下来cd
匹配?!ab
成功?!ab
是零宽的,所以接下来仍然从index = 2
处尝试(也就是c
继续匹配c
),所以匹配成功,接下来d
也匹配d
成功,全部表达式匹配完毕,最终返回成功结果(index = 0
,由于没有失败)接下来一些实战练习,加深印象
ing
单词的前缀需求说明
例如: I am reading in the dining room
的匹配结果应该是read
与din
代码
var str="I am reading in the dining room"; var reg=/\w+(?=ing)/g; str.match(reg); // ["read", "din"]
说明
\w+
匹配至少一个以上的单词?=ing
表明右侧必须有ing
,可是匹配的结果又不包含ing
g
是全局匹配.css
后缀,但又不能是.min.css
需求说明
这道题曾屡次出如今各大平台,基本都是依靠零宽断言来检测,例如:
test('a.min.css'); // false test('b.css'); // true test('c.mining.css'); // true
代码
var reg=/^(?!.*\.min\.css$).+\.css$/; reg.test('a.min.css'); // false reg.test('.min.css'); // false reg.test('.css'); // false reg.test('min.css'); // true reg.test('b.css'); // true reg.test('c.mining.css'); // true
说明
因为只考虑单个文件名的匹配,因此较简单
?!.*\.min\.css
负向先行断言试探文件名。这一步匹配完后,直接就排除了xxx.min.css
了(因为是*
,因此.min.css
也会匹配失败)\w+.*\.css
匹配xxx.css
这种状况.css
但不匹配.min.css
RegexBuddy
等工具自行检测.min.css
文件的文件名需求说明
例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css
(文件以;
隔开)的匹配结果应该是a
与e.a
代码
var str="a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css"; var reg=/\w+[^;]*(?=\.min\.css)/g; str.match(reg); // ["a", "e.a"]
说明
这类型表达式回溯次数不少,实际中能够有更好的解决方案,好比先分割,再匹配
\w+[ ^;]*
确保了必须是一个正常的单词开头,而且不能包括;
,因此直接排除了名字以.
开头或名字中包含;
的状况?=\.min\.css
确保名字右侧必须有.min.css
a
与e.a
符合状况.css
文件的文件名,须要排除.min.css
需求说明
这道题基于上两题的综合与变形,增长了点难度(再也不是单个文件名匹配,而是字符串中的文件名提取)
例如: a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css
(文件以;
分割)的匹配结果应该是min
、b
、d
和f.min.a
代码
var str = "a.min.css;.min.css;.css;min.css;b.css;c.min.js;d.css;e.a.min.css;f.min.a.css"; var reg1 = /[^;]+(?=\.css)/g; var match1 = str.match(reg1); var reg2 = /\.min$/; var match2 = []; match1 && match1.map(function(item, index) { !reg2.test(item) && match2.push(item); }); console.log(match1); // ["a.min", ".min", "min", "b", "d", "e.a.min", "f.min.a"] console.log(match2); // ["min", "b", "d", "f.min.a"]
说明
好吧,我认可没法只靠一个表达式实现这个功能(不知道在座的各位有谁能够的...)
[ ^;]+(?=\.css)
先匹配全部的.css
后缀的名字\.min$
剔除以.min
结尾的名字.css
但非.min.css
)也行PS:原本准备一步解决做为压轴的,可是尝试了好久都未果,最终仍是拆分来实现的,之因此仍然放在最后,也算是给本身一个警醒
深刻研究后,才发现精通正则表达式真的很难,不少时候,你认为的已经精通了
只是一种假象。
所以,仍是放下身段,努力学习吧!
初次发布2017.07.26
于我的博客
http://www.dailichun.com/2017/07/26/regularExpressionZeroWidthAssertion.html