js正则表达式-前瞻

本文介绍了JavaScript正则表达式中的前瞻概念,通过实例解释了正向和负向前瞻的工作原理,并展示了如何利用前瞻解决HTML标签过滤的需求,包括保留特定标签并替换其他标签的情况。

js正则表达式-前瞻

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

一、标签过滤需求

不知道大家在写JS有没有遇到过这样的情况,当你要处理一串字符串时,需要写一个正则表达式来匹配当中不是 XXX 的文本内容。听起来好像略有些奇怪,匹配不是 XXX 的内容,不是 XXX 我匹配它干嘛啊,我要啥匹配啥不就完了。你还别说,这个玩意还真的有用,不管你遇没遇到过,反正我是遇到了。具体的需求例如:当你收到一串HTML代码,需要对这一串HTML代码过滤,将里面所有的非<p>标签都改为<p>。这里肯定有不少同学就要嫌弃了,将所有标签都改为<p>,那就把任意标签都改为<p>不就完了?”,于是乎一行代码拍脑袋而生:

    var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
    var reg = /<(\/?).*?>/g;
    var newStr = str.replace(reg, "<$1p>");
    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>,替换掉其他的标签。那么问题也就来了,如何排除?

二、正则前瞻表达式

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

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

由于 JS 原生不支持后瞻,所以这里就不研究它了。我们来看看前瞻的作用:

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

在这个DEMO中我们可以看出正向前瞻的作用,同样是字符”H”,但是只匹配”H”后面紧跟”i”的”H”。就相当于有一家公司reg,这时候有多名”H”人员前来应聘,但是reg公司提出了一个硬条件是必须掌握”i”这项技能,所以”Hello”就自然的被淘汰掉了。

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

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

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

三、前瞻的非捕获性

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

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

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

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

注意其中输出的语句,前面我们可以看到,如果引用符没有匹配到指定的字符,那么就会显示空串”“,可是这里是直接显示了整个引用符”$1”。这是因为前瞻表达式根本就没有捕获,没有捕获也就没有引用。

非捕获性是前瞻的一个基本特征,前瞻的另外一个特性是不吃字符,意思就是前瞻的作用只是为了匹配满足前瞻表达式的字符,而不匹配前瞻本身。也就是说前瞻不会修改匹配位置,这么说我自己都觉得晦涩,我们还是来看看代码吧

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

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

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

四、用前瞻实现标签过滤

既然前瞻是非捕获性的,而且还不吃字符,那么了解到这些特征后我们现在终于可以完成我们的需求了吧?因为它不吃字符,所以具体的标签字符还得由我们自己来吃:

    var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
    var reg = /<(\/?)(?!p|\/p).*?>/g;
    var newStr = str.replace(reg, "<$1p>");
    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个,也就是没有匹配到字符,所以后面的引用是个空串。

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

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

    var str = '<div>,<p>,<h1>,<span>,</span>,</h1>,</p>,</div>';
    var reg = /<(\/?)(?!p|\/p|div|\/div).*?>/g;
    var newStr = str.replace(reg, "<$1p>");
    console.log(newStr);//<div>,<p>,<p>,<p>,</p>,</p>,</p>,</div>
<think>嗯,用户想在ES5标准下使用JavaScript的正则表达式实现前瞻功能。首先,我需要确认ES5是否支持正则前瞻断言。根据我的记忆,ES5其实是支持正向和负向的前瞻断言的,不过可能用户混淆了ES版本的支持情况。比如,ES6引入了后行断言,而ES5可能只有前瞻断言。 接下来,我需要验证这一点。查阅资料发现,JavaScript的正则表达式前瞻断言确实在ES5中就支持了。正向前瞻是`(?=...)`,而负向前瞻是`(?!...)`。而后行断言(lookbehind)如`(?<=...)`和`(?<!...)`是在ES2018(ES9)才加入的,所以ES5不支持后行断言。用户可能误以为前瞻也是ES6的功能,但实际在ES5中已经可用。 用户的问题可能是想了解如何在ES5中使用前瞻,或者可能遇到了相关问题。比如,他们可能尝试使用后行断言,但发现ES5不支持,所以需要替代方案。不过用户明确提到的是前瞻功能,所以需要解释ES5中如何正确使用前瞻断言。 然后,我需要给出具体的用法示例。比如,匹配后面跟着特定模式的字符串,比如匹配字母后面跟数字的情况,可以用`/[a-z](?=\d)/i`。同时,提醒用户注意ES5不支持后行断言,如果需求中有后行部分,可能需要其他方法,比如捕获组或替换函数。 另外,用户可能在处理密码复杂度验证等场景时遇到问题,这时候使用前瞻断言可以方便地检查多个条件。例如,验证密码必须包含数字、大写字母和特殊符号,可以用多个正向前瞻来确保每个条件都满足。 还要注意在回答中引用相关的资料,比如用户提供的引用[3]提到ES2018增加了后行断言,所以需要指出ES5不支持这部分,但前瞻是可用的。同时,确保回答结构清晰,分点说明用法和示例,帮助用户逐步理解和应用。</think>在ES5标准下,JavaScript正则表达式支持使用前瞻断言(Lookahead Assertions),但不支持后瞻断言(Lookbehind Assertions)。以下是具体实现方法和注意事项: ### 一、前瞻断言的使用方法 1. **正向前瞻** `(?=pattern)` 匹配后面跟着特定模式的位置: ```javascript // 匹配"美元符号后跟数字"的案例 var str = "price$100 tax$20"; var regex = /\$(?=\d+)/g; str.match(regex); // 返回 ["$", "$"] ``` 2. **负向前瞻** `(?!pattern)` 匹配后面不跟特定模式的位置: ```javascript // 匹配"q后面不跟u"的字符 var str = "qiqi queue qatar"; var regex = /q(?!u)/g; str.match(regex); // 返回 ["q", "q"](匹配"qiqi"中的第二个q和"qatar"的q) ``` ### 二、ES5使用限制和解决方案 1. **不支持后瞻断言** ES5无法使用`(?<=...)`和`(?<!...)`语法。若需要反向验证,可通过捕获组变通实现: ```javascript // 匹配"数字前的美元符号"(模拟后瞻) var str = "$100 ¥200"; var regex = /(\$)(\d+)/; str.match(regex)[1]; // 捕获$符号 ``` 2. **多条件验证技巧** 使用连续正向前瞻实现复合条件验证: ```javascript // 密码复杂度验证(必须包含大写字母、数字、特殊符号) var regex = /^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/; regex.test("Passw0rd!"); // true ``` ### 三、典型应用场景 1. **表单验证** ```javascript // 验证含至少两种字符类别的密码 const pwdRegex = /^(?=.*[a-z])(?=.*\d|.*[A-Z]).{6,}$/; ``` 2. **字符串解析** ```javascript // 提取价格数值(匹配数字前的货币符号) const priceStr = "USD123,EUR456"; const usdRegex = /USD(?=\d+)/g; priceStr.match(usdRegex); // ["USD"] ``` 3. **语法高亮** ```javascript // 匹配CSS颜色值(#开头且跟随3/6位十六进制) const colorRegex = /#(?=[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\b)[0-9a-fA-F]{3,6}/g; ``` ### 四、性能注意事项 - 复杂正则表达式可能导致回溯问题,建议先用简单模式过滤 - 避免在循环中重复创建正则对象 - 优先使用锚点符`^`和`$`限定匹配范围[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值