JS 正则表达式否认匹配(正向前瞻)

引言

  JS 正则表达式是 JS 学习过程当中的一大难点,繁杂的匹配模式足以让人头大,不过其复杂性和其学习难度也赋予了它强大的功能。文章从 JS 正则表达式的正向前瞻提及,实现否认匹配的案例。本文适合有必定 JS 正则表达式基础的同窗,若是对正则表达式并不了解,还需先学习基础再来观摩这门否认大法。html


 1、标签过滤需求

  不知道你们在写JS有没有遇到过这样的状况,当你要处理一串字符串时,须要写一个正则表达式来匹配当中不是 XXX 的文本内容。听起来好像略有些奇怪,匹配不是 XXX 的内容,不是 XXX 我匹配它干吗啊,我要啥匹配啥不就完了。你还别说,这个玩意还真的有用,无论你遇没遇到过,反正我是遇到了。具体的需求例如:当你收到一串HTML代码,须要对这一串HTML代码过滤,将里面全部的非<p>标签都改成<p>。这里确定有很多同窗就要嫌弃了,“将全部标签都改成<p>,那就把任意标签都改成<p>不就完了?”,因而乎一行代码拍脑壳而生:web

1     var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
2     var reg = /<(\/?).*?>/g;
3     var newStr = str.replace(reg, "<$1p>");
4     console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,</p>,</p>

  注意这个方法中有一个引用符 “$1” ,这个的意思引用正则的表达式的第1个分组,能够用$N来表示在正则表达式中的第N个捕获的引用。就那上面的例子来讲,"(\/?)"这个一个表达式的含义是,"\/"这个字符出现0次或者1次,而$1这个引用呢就至关于和“\/”这个字符门当户对的大闺女,她已下定决心今生非"\/"不嫁。因此当匹配到有一个“\/”的时候,$1这个引用就把它捕获下来,从如今起,你的就是个人,个人就是你的啦,所以$1等价于"(\/?)"所匹配到的字符;反之若是没有匹配到"\/"这个字符,那$1这个引用就得空守闺房,独立熬过一个又一个漫长的夜晚,由于它心里极度的空虚,因此$1就等价于""(也就是空串)。正则表达式

  这里先聊了聊引用和捕获的概念,由于后面还会用到它。那么话说回来,刚才那一串正则,不是已经完美的实现了需求了吗?还研究什么否认匹配啊?各位看官别急,且听小生慢慢道来。咱们都知道,需求这个东西,确定是会改嘀(◐ˍ◑)。如今改一改需求:当你收到一串HTML代码,须要对这一串HTML代码过滤,将里面全部的非<p>或者<div>标签都改成<p>。WTF?这算哪门子需求?话说我当时也是这种反应。咱们如今分析一下这个需求到底要干吗,也就是说,保留原HTML代码中的<p>和<div>,将其余标签统一修改成<p>。咦...这下可很差弄了,刚才那串代码看上去貌似行不通了。因此说这时候就只能用排除法了,排除掉<p>和<div>,替换掉其余的标签。那么问题也就来了,如何排除?express

 2、正则前瞻表达式

  在正则表达式当中有个东西叫作前瞻,有的管它叫零宽断言:浏览器

表达式 名称 描述
(?=exp) 正向前瞻 匹配后面知足表达式exp的位置
(?!exp) 负向前瞻 匹配后面不知足表达式exp的位置
(?<=exp) 正向后瞻 匹配前面知足表达式exp的位置(JS不支持
(?<!exp) 负向后瞻 匹配前面不知足表达式exp的位置(JS不支持

  因为 JS 原生不支持后瞻,因此这里就不研究它了。咱们来看看前瞻的做用:学习

1     var str = 'Hello, Hi, I am Hilary.';
2     var reg = /H(?=i)/g;
3     var newStr = str.replace(reg, "T");
4     console.log(newStr);//Hello, Ti, I am Tilary.

  在这个DEMO中咱们能够看出正向前瞻的做用,一样是字符"H",可是只匹配"H"后面紧跟"i"的"H"。就至关于有一家公司reg,这时候有多名"H"人员前来应聘,可是reg公司提出了一个硬条件是必须掌握"i"这项技能,因此"Hello"就天然的被淘汰掉了。spa

  那么负向前瞻呢?道理是相同的:code

1     var str = 'Hello, Hi, I am Hilary.';
2     var reg = /H(?!i)/g;
3     var newStr = str.replace(reg, "T");
4     console.log(newStr);//Tello, Hi, I am Hilary.

  在这个DEMO中,咱们把以前的正向前瞻换成了负向前瞻。这个正则的意思就是,匹配"H",且后面不能跟着一个"i"。这时候"Hello"就能够成功的应聘了,由于reg公司修改了他们的招聘条件,他们说"i"这门技术会有损公司的企业文化,因此咱们不要了。htm

3、前瞻的非捕获性

  说到这里,让咱们回到最初的那个需求,让咱们先用负向前瞻来实现第一个需求:将全部非<p>标签替换为<p>。话说同窗们刚学完了负向前瞻,了解到了JS的博大精深,心中暗生窃喜,提笔一挥:blog

1     var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
2     var reg = /<(\/?)(?!p)>/g;
3     var newStr = str.replace(reg, "<$1p>");
4     console.log(newStr);//<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>

  What?为何不起做用呢?说好的否认大法呢?这里就得聊一聊前瞻的一个特性,前瞻是非捕获性分组,什么玩意是非捕获性分组呢?还记得前面那位非"\/"不嫁的大闺女$1吗,人家为何那么一往情深,是由于她早已将"\/"的心捕获了起来,而前瞻倒是非捕获性分组,也就是你捕获不到人家。也就是说没法经过引用符"\n"或者"$n"来对其引用:

1     var str = 'Hello, Hi, I am Hilary.';
2     var reg = /H(?!i)/g;
3     var newStr = str.replace(reg, "T$1");
4     console.log(newStr);//T$1ello, Hi, I am Hilary.

  注意其中输出的语句,前面咱们能够看到,若是引用符没有匹配到指定的字符,那么就会显示空串"",但是这里是直接显示了整个引用符"$1"。这是由于前瞻表达式根本就没有捕获,没有捕获也就没有引用。

  非捕获性是前瞻的一个基本特征,前瞻的另一个特性是不吃字符,意思就是前瞻的做用只是为了匹配知足前瞻表达式的字符,而不匹配前瞻自己。也就是说前瞻不会修改匹配位置,这么说我本身都以为晦涩,咱们仍是来看看代码吧︽⊙_⊙︽:

1     var str = 'Hello, Hi, I am Handsome Hilary.';
2     var reg = /H(?!i)e/g;
3     var newStr = str.replace(reg, "T");
4     console.log(newStr);//Tllo, Hi, I am Handsome Hilary.

  注意观察输出的字符串,前瞻的做用仅仅是匹配出知足前瞻条件的字符"H",匹配出了"Hello"和"Handsome"当中的H,但同时前瞻不会吃字符,也就是不会改变位置,接下来仍是会紧接着"H"开始继续往下匹配,这时候匹配条件是"e",因而"Hello"中的"He"就匹配成功了,而"Handsome"中的"Ha"则匹配失败。

1. /H(?!i)/g --> Hello, Hi, I am Handsome Hilary.
2. /H(?!i)e/g --> Hello, Hi, I am Handsome Hilary.

 4、用前瞻实现标签过滤

  既然前瞻是非捕获性的,并且还不吃字符,那么了解到这些特征后咱们如今终于能够完成咱们的需求了吧?由于它不吃字符,因此具体的标签字符还得由咱们本身来吃:

1     var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
2     var reg = /<(\/?)(?!p|\/p).*?>/g;
3     var newStr = str.replace(reg, "<$1p>");
4     console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,</p>,</p>

  聊了这么半天,终于解决了我们的第一个需求,注意当中的".*?",虽然这里匹配的是任意字符,可是别忘了,有了前面的负向前瞻,咱们匹配到的都是后面不会紧跟着"p"或者"/p"的字符"<"。

/<(?!p|\/p)/g --> <div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>

  注意在这里用了一个管道符"|"来匹配"\/p",虽然前面已经有了"(\/?)"匹配结束符,可是切记这里的分组选项不能省略,由于这里的量词是能够出现0次。咱们来试想一下若是用"/<(\/?)(?!p).*?>/g"来匹配"</p>"这个标签,当量次匹配到"/"的时候,发现能够匹配,便记录下来,而后对"/"进行前瞻判断,可是后面却接着一个"p"因而不能匹配,丢掉;注意这时"(\/?)"的匹配字符是0个,因而乎转而对"<"进行前瞻判断,这里的"<"后面紧接着的是"/p"而不是"p",因而乎成功匹配,因此这个标签会被替换掉;并且,因为以前的分组匹配到的字符是0个,也就是没有匹配到字符,因此后面的引用是个空串。

1     var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
2     var reg = /<(\/?)(?!p).*?>/g;
3     var newStr = str.replace(reg, "<$1p>");
4     console.log(newStr);//<p>,<p>,<p>,<p>,</p>,</p>,<p>,</p>

  完成了第一个过滤需求,那么第二个过滤需求也就天然而然的完成了,这时候,就算有那么五六个标签须要保留,我们也不用怕了:

1     var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
2     var reg = /<(\/?)(?!p|\/p|div|\/div).*?>/g;
3     var newStr = str.replace(reg, "<$1p>");
4     console.log(newStr);//<div>,<p>,<p>,<p>,</p>,</p>,</p>,</div>

 总结

  JS 的正向前瞻只是正则表达式当中一部分,没至关就这么一部分还有着这么多的奥妙呢。

  在使用正向前瞻,咱们须要注意的是:

  •   前瞻是非捕获性的:其特征是没法引用。
  •   前瞻不消耗字符:前瞻只匹配知足前瞻表达式的字符,而不匹配其自己。

  话说,我们的需求就到这了吗?真的就完了吗?同窗们以为过瘾不?有些同窗以为可能差很少了,须要消化一段时间,可是绝对有那么一部分同窗还彻底没过瘾呢,不要紧,最后留给你们一道思考题,截止到我写这篇博客为止,我尚未想出一个解决办法呢(ง •_•)ง。

  需求以下:当你收到一串HTML代码,须要对这一串HTML代码过滤,将里面全部的非<p>或者<div>标签都改成<p>,而且保留全部标签的样式,要求只使用一个正则表达式,例如:

//输入
var input = '<div class="beautiful">,<p class="provocative">,<h1 class="attractive" id="header">,<span class="sexy">,</span>,</h1>,</p>,</div>';
//输出
var output = '<div class="beautiful">,<p class="provocative">,<p class="attractive" id="header">,<p class="sexy">,</p>,</p>,</p>,</div>';

  若是你有好的解决方案,欢迎在评论区留言,你们一块儿学习。

参考文献:

  devinran —— 《相爱相杀——正则与浏览器的爱恨情仇》

  Barret Lee —— 《进阶正则表达式》

相关文章
相关标签/搜索