本文作者:IMWeb wonghan 原文出处:IMWeb社区 未经同意,禁止转载
最近在做正则表达式的习题,题目本身不难,但我做完习题后难免脑洞大开举一反三,而这次,被我自己的脑洞难住了。
题目如下:当你收到一串HTML代码,需要对这一串HTML代码过滤,将里面所有的非<p >标签都改为<p>标签
如果不了解 正向前瞻 或者 对这道题目没有什么头绪 ,可以看下面这篇文章。
附参考文献地址:https://www.cnblogs.com/dong-xu/p/6926064.html
因此一般解题的代码为:
var str = '<p>,</p>,</a>,</ p>';
var reg = /<(\/?)(?!p|\/p).*?>/g;
var newStr = str.replace(reg, "<$1p>");
console.log(newStr);
//<p>,</p>,</p>,</p>
我不禁产生疑问:如果将reg改为:
var reg = /<(\/?)(?!p).*?>/g;
按照逻辑分析来说,结果也是一样的。但是,结果却大跌眼镜:
var str = '<p>,</p>,</a>,</ p>';
var reg = /<(\/?)(?!p).*?>/g;
var newStr = str.replace(reg, "<$1p>");
console.log(newStr);
//<p>,<p>,</p>,</p>
可以看出,位于第二项的“</p>”变成了“<p>”。如果从逻辑上分析,如果“<(\/?)”对应“</”的情况下,因为"</"后面是“p”,所以最终不会被匹配到的(......①),但结果却不符。
因此便产生了疑问:为什么会这样?若您也有相同疑问,且慢慢往下看。
借助https://regex101.com/ 我们来慢慢分析一下
可以看出,</p>被匹配到,让我们简化一下正则表达式分析一下:
可以看出,位于第二项的“</p>”:“<(\/?)”对应“<”而不是“</”,因此后面是“/p”而不是"p"(符合断言“(?!p)”),而再后面的“.*?”即对应"/p",最后的“>”对应“>”。(......②)
*是否会产生疑问:以上的情况(......①)(......②),逻辑分析上来说均是说得通的,但正则表达式偏偏选择第②种情况,这是为什么呢?
其实,不妨看看网站右边的词条解释
可见,正则表达式按照字符匹配,先匹配"<",再匹配“(\/?)”,之后匹配“(?!p)”,然后匹配“.*?”,最后匹配“>”。
其中,造成以上疑问的罪魁祸首是“?”,看看词条的解释:“? Quantifier — Matches between zero and one times, as many times as possible, giving back as needed (greedy)”。意思是:在“/”出现0次或1次这两种情况下匹配,判断各情况下的匹配情况,然后返回符合匹配且是贪婪匹配的情况(默认情况下是贪婪匹配)
1.以</p>为例,分2种情况讨论:
(1)匹配“<” --> "/"出现0次 --> "/p”不是“p”,符合断言 -->符合匹配;
(2)匹配“<” --> "/"出现1次 --> "p”等于“p”,不符合断言 -->不符合匹配;
因此,可得出结论:选择情况(1),与实际测试结果符合。
1.以</a>为例,分2种情况讨论:
(1)匹配“<” --> "/"出现0次 --> "/a”不是“p”,符合断言 -->符合匹配;
(2)匹配“<” --> "/"出现1次 --> "a”不是“p”,符合断言 -->符合匹配;
因此,根据贪婪匹配原则,选择情况(2),与实际测试结果符合。
正则表达式的基本语法、属性与方法、分组与捕获、引用与反向引用、贪婪匹配与惰性匹配、正向前瞻与负向前瞻、String方法的正则用法等,需要在理解的基础上,融会贯通,才能更好的掌握。
若对正则表达式有疑问便借助:https://regex101.com/ 进行分析。
愿你我在前端之路上昂首向前,共同进步。