发布网友 发布时间:2024-09-29 10:36
共1个回答
热心网友 时间:2024-09-29 13:22
1、定义1.1什么是断言?断言用于查找某些内容或内容所在的位置,该内容或内容所在位置应满足一定的条件。
那么什么叫零宽断言呢?零宽又是什么意思?
1.2零宽断言通俗的讲,零宽断言的字面意思是匹配宽度为零的断言。
所谓匹配的宽度为零,就是说这种断言在工作时:
只作为匹配的判断条件
实际不匹配任何的结果
也不捕获任何的内容
这三个点,既展示了零宽断言的定义,也说明了零宽断言的两个特性:非捕获性和不吃字符
这两个特性我放在下个章节来讲。
2、零宽断言表达式与案例因为后面有一些案例代码,为了能让大家更清楚的理解代码,所以这个章节先讲一下零宽断言表达式
2.1表达式表达式名称描述(?=exp)正向(正预测)先行断言匹配某个位置,该位置的后面的内容应匹配表达式exp(?!exp)负向(负预测)先行断言匹配某个位置,该位置的后面的内容不匹配表达式exp(?<=exp)正向(正回顾)后发断言匹配某个位置,该位置的前面的内容应匹配表达式exp(JS不支持)(?<!exp)负向(负回顾)后发断言匹配某个位置,该位置的前面的内容不匹配表达式exp(JS不支持)相信你看这个表格就能理解十之八九了,我这里举个小例子
2.2案例解析现在有一个需求是这样的:
在字符串'北京市(朝阳区)(西城区)(海淀区)'中,取出没有被()包裹的字符北京市
这怎么做呢?
要求是取出没有被()包裹的字段,那这个字段的后面紧跟着的一定是(,就是这么简单。
那我可以写一个零宽断言表达式varreg=/.*?(?=\()/来看看能不能满足需求:
console.log(reg.exec('北京市(朝阳区)(西城区)(海淀区)'))//["北京市",index:0,input:"北京市(朝阳区)(西城区)(海淀区)",groups:undefined]console.log('北京市(朝阳区)(西城区)(海淀区)'.match(reg))//["北京市",index:0,input:"北京市(朝阳区)(西城区)(海淀区)",groups:undefined]简单做个解释,这个reg可以看作两部分,分别是.*?和(?=\()
假设你已经知道了在正则表达式当中:
.代表匹配除了换行和行结束符的任意字符
*代表匹配任意次数,保证我们得到的是北京市而不是市这个单字
?代表对多个连续的值,要么匹配0次,要么匹配1次,最多匹配一次
那么前一部分就可以匹配给定字符中包含的所有字符,而后一部分零宽断言则给匹配添加了条件,要求匹配的内容后面跟着(,于是我们就能得到想要的结果
2.3贪婪模式与非贪婪模式你可能有疑问,如果我正则写成这个样子/.*(?=\()/,为什么结果就不对?
console.log('北京市(朝阳区)(西城区)(海淀区)'.match(/.*(?=\()/))//["北京市(朝阳区)(西城区)",index:0,input:"北京市(朝阳区)(西城区)(海淀区)",groups:undefined]我首先说明,这所以在这里加入贪婪模式与非贪婪模式,是因为上面的案例是正向(正预测)先行断言的案例而网上有些人说正向零宽断言是从右向左执行的,我就不批判了,只希望大家能够擦亮眼睛。
那么为什么少了一个问号,得出了全然不同的结果呢?
上面说了,?在这个表达式里,表示要么不匹配,要么最多匹配一次,这就是非贪婪模式
而不添加?,表达式以贪婪模式运行。什么是贪婪模式?就是尽可能多的去匹配,匹配到了还去匹配,贪得无厌,一直匹配到字符串的末尾
因为字符串中有三个(,贪婪模式下会一直匹配到字符串结尾,就会得到北京市(朝阳区)(西城区)这个结果
3、零宽断言的特性第一章已经说了零宽断言定义和它的特性,这里来详细讲讲它的这两个特性:非捕获特性和不吃字符特性
3.1非捕获特性这是零宽断言的一个基本特性,非捕获特性
我假设你知道在正则表达式中可以使用()来进行分组取值,一个()就是一个分组。所以我这里举一个使用分组取值替换字符的例子:
varstr='Hello,Wrld!';varreg=/(W)/g;console.log(str.replace(reg,"$1o"))//Hello,world!上面的代码,我使用分组来获取W并将它成功替换成正确的字符Hello,world!现在改一下代码,看看会怎样:
varstr='Hello,Wrld!';varreg=/W(?=r)/g;console.log(str.match(reg))//["W"]//注意看使用match方法返回了正确的结果,那么我能替换成功吗console.log(str.replace(reg,"$1o"))//Hello,$1orld!很显然,虽然str.match方法返回了正确的结果,但是替换并没有实现想要的效果并且这个结果和使用非捕获性分组是一致的:
varstr='Hello,Wrld!';varreg=/(?:W)/g;console.log(str.replace(reg,"$1o"))//Hello,$1orld!所以我们得出结论:
虽然第二个代码片段也使用了()进行分组,但因为这个分组使用的是零宽断言的表达式,正则并没有返回针对$1的引用,所以替换是不能成功的。
由此,这个例子很好的证明了零宽断言具有非捕获的特性。
3.2不吃字符特性零宽断言的另一个特性,是不吃字符。
这里的不吃字符不是寻常意义上的吃,而是说零宽断言本身不占据查找位置,它不算是要被查找的内容,因此,正则表达式并不匹配零宽断言本身。
这里我们先引申一个概念。
这个概念,是正则对象的一个属性,叫做lastIndex。它规定了正则表达式下一次匹配的起始位置。
如果我们使用正则的方法如test,exec等方法,就能清楚的看到lastIndex的影响:
varreg=/([a-z])/g;vararr=["a","b","c","d","e"];for(vari=0;i<arr.length;i++){console.log(reg.exec(arr[i]))}这段代码的返回结果,相信很多人都会判断错误,我直接把结果放在下面
["a","a",index:0,input:"a",groups:undefined]
null["c","c",index:0,input:"c",groups:undefined]null["e","e",index:0,input:"e",groups:undefined]
是不是惊掉了一地下巴?
这就是lastIndex在搞怪,简单的说,当正则全局匹配成功时,lastIndex就指向了匹配成功的字符索引,再次匹配时,则从lastIndex位置继续向后匹配
注意数组中每一项的长度都是1,如果lastIndex的值大于等于1,自然匹配的结果就是null;没有匹配到结果,lastIndex的值就成了默认值0,下一次的匹配就是非null的结果了。
所以我们得出结论,lastIndex随着匹配结果的变化而变化
下面我们使用零宽断言的方式来试试。
varreg=/(?=[a-z])/g;vararr=["a","b","c","d","e"];for(vari=0;i<arr.length;i++){console.log(reg.exec(arr[i]))}这个结果不知道你能猜出来吗?
["",index:0,input:"a",groups:undefined]
["",index:0,input:"b",groups:undefined]["",index:0,input:"c",groups:undefined]["",index:0,input:"d",groups:undefined]["",index:0,input:"e",groups:undefined]
而因为正则表达式不匹配零宽断言本身,因此零宽断言不会改变lastIndex的值,所以它返回了空值“”,却没有完全返回空(null)。
如果你依然不能理解,尝试看看下面这段代码:
varstr='hello,thisishierry,andthatishi,thereishandsome'varreg=/h(?!i)e/console.log(str.match(reg))这段代码十分简单,结果只会匹配一项
["he",index:0,input:"hello,thisishierry,andthatishi,thereishandsome",groups:undefined]
很明显,reg匹配的时候越过了表达式中间的i,只匹配了h和e,这能更明显的体现不吃字符的特性。
4、零宽断言的四种类型通常,零宽断言依据断言的方式被分为四类,分别是:
零宽度正向先行断言(也叫正预测先行断言)
零宽度负向先行断言(也叫负预测先行断言)
零宽度正向后发断言(也叫正回顾后发断言)
零宽度负向后发断言(也叫负回顾后发断言)
在javascript这门语言中,只支持先行断言,不支持后发断言
4.1正向断言和负向断言有什么区别呢?正向断言是当某个位置前面或者后面的内容要匹配表达式exp,才会返回非空的结果
负向断言是当某个位置前面或者后面的内容不匹配表达式exp,才会返回非空的结果
4.2先行断言和后发断言的区别?先行断言是用某个位置后面的内容与表达式exp进行匹配
后发断言是用某个位置前面的内容与表达式exp进行匹配
4.3零宽度正向先行断言例子一,是我在面试题里找到的
衣服38元、鞋子62元、剪发15元、吃饭45元、打车30元、冰激凌10元,对花费的金钱求和?
答案:/\d.?\d(?=[元块])/g
vars='衣服38元、鞋子62元、剪发15元、吃饭45块、打车30元、冰激凌10元'varr=/\d.?\d(?=[元块])/gvarsum=s.match(r).reduce(function(prev,cur){returnprev*1+cur*1;})console.log(sum);//200例子二
'ilikeeating、sleeping、seemoviesand'中拿到以ing结尾的动作单词
答案:/\w+(?=ing)/g
vars='ilikeeating、sleeping、seemoviesand'varr=/\w+(?=ing)/gconsole.log(s.match(r))//["eat","sleep"]这两个例子比较简单,就不坐过多解释了。
4.4零宽度负向先行断言举个例子,也是我在面试题中找到的
测试一个文件是否是.css后缀,但又不能是.min.css,如:
test('a.min.css');//falsetest('b.css');//truetest('c.mining.css');//true
答案:/^(?!.*\.min\.css$).+\.css$
这里先对以任意字符开头结尾为.min.css的文件名做了排除,在剩余的文件名中查找以.css为结尾的文件名
console.log('北京市(朝阳区)(西城区)(海淀区)'.match(/.*(?=\()/))//["北京市(朝阳区)(西城区)",index:0,input:"北京市(朝阳区)(西城区)(海淀区)",groups:undefined]05、结尾创作不易,如果这篇文章帮助到了你,请动动手指点个赞再走吧~
作者:晴天同学