正则表达式的理解和初步优化

2019-05-07  本文已影响0人  小a草
正则表达式的优化

为了高效地使用正则表达式,首先要理解它的工作原理。

当你创建一个正则表达式对象(使用正则直接量或 RegExp 构造函数),浏览器会验证你的表达式,然后把它转化为一个原生代码程序,用于执行匹配工作。如果你把正则对象赋值给一个变量,可以避免重复执行这一步骤。

当正则类进入使用状态,首先要确定目标字符串的起始搜索位置,它是字符串的起始字符,或者由正则表达式的lastIndex 属性指定,但是当它从第四步返回到这里时,此位置则在最后一次的起始位置的下一位字符分类的位置上。
浏览器厂商优化正则表达式引擎的办法是,通过提前决定跳过一些不必要的步骤,来避免大量无意义的工作。例如,如果正则表达式由 ^ 开始,IE和chrome通常会判断字符串的起始位置能否匹配,如果匹配失败,那么可以避免愚蠢地搜索后续位置。另一个例子是匹配第三个字符x的字符串,一个聪明的做法是先找到x ,然后再将起始 位置回退两个字符。

一旦正则表达式知道开始位置,他会逐个检查文本和正则表达式模式。当一个特定字原匹配失败时,正则表达式会试着回溯到之前尝试匹配的位置上,然后尝试其他可能的路径。

如果在字符串当前的位置发现了一个完全匹配,那么正则表达式宣布匹配成功。如果正则表达式所有的可能路径都没有匹配到,正则表达式引引擎会回退到第二步,然后从下一个字符重新尝试。当字符串的每个字符串(以及最后一个字符串后面的位置)都经历这个过程,如果还没有成功匹配,那么正则表达式就宣布彻底匹配失败。

正则表达式匹配目标字符串的方式

  • 从左到右逐个测试表达式。
  • 遇到量词(* , + ? 或者 {3,4})需要决定何时尝试匹配更多字符。
  • 遇到分支 ( 来自 | 操作符 ),需要从可选项中选择一个尝试匹配
// 正则部分
var reg = /a(b|c) de/
var str = "ab ee,ac de"
str.test(str)

该正则表达式匹配 “ac de"。匹配过程开始时,首先会查找a,目标字符串的首字母a符合匹配,于是立即被找到。接下来,子表达式(b|c)提供了两个选择属于分支类型,根据从左到右匹配的原则,选择左边部分b, 在str中第二部分匹配成功,随后继续空格匹配,然后正则部分的d去和str中的e匹配,匹配失败。

此时表达式不能放弃,还有其他情况需要匹配

此时发生回溯,回溯不是从头进行重新匹配,回溯到从第一个分支决策点进行匹配,显然匹配失败,

这时已无更多决策点,说明字符串从第一个位置匹配失败,

将其匹配位置移到第二个字符,和正则中的首字符匹配失败,继续后移,直到下次和正则中的首字符a匹配, 此时字符串str还可以匹配的字符"ac de"
接着正则reg选择左分支b, b和str中的c不匹配,继续回溯到上次决策的位置,匹配右分支c和str中的c匹配,继续匹配空格,d,e 完全匹配 。该正则表达式匹配结束
str.test(str) 返回 true.

var str = "<p>test </p>"+
          "<img src='123.png'>"+
          "<div>div</div>";
var reg = /<p>.*</p>/i
reg.test(str)

该正则表达式开始从左向右匹配,匹配了< p > ,然后匹配 .* 。点号(.)能匹配除换行符意外的任意字符,贪婪量词(*)表示重复零次或多次----尽可能匹配多次。因为目标字符串中无换行符,那么它会吞并所有字符!因此正则表达式在字符串末尾匹配失败,因为正则表达式并未完成,需要尝试匹配<,

正则表达式并未完成,需要尝试匹配<

表达式每次回溯一个字符 ,继续尝试匹配 < ,直到回溯到 < /div > 标签的首字符 < 。然后匹配 / ,匹配成功,然后匹配p 失败。

正则表达式继续回溯,

并重复这一过程,直到回溯匹配到第一段的< /p >结束。

这里可以看出 (.*)这种贪婪的匹配方式并非我们想要的,所以可以替换为 (非贪婪)量词 (*?) 以匹配单个段落。

如果目标字符串只有一个段落,你会发现贪婪模式和惰性模式正则表达式时等价的。但他们的匹配过程并不相同。

回溯失控

当正则表达式导致你的浏览器假死数秒,数分钟,甚至更长时间,问题很可能是因为回溯失控,例如:

var reg = /<html>[\s\S]*?<head>[\s\S]*?<\/head>[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/

这段表达式匹配常规的HTML字符时运行正常,但是当目标字符串缺少一个或多个必要的标签时会变得很糟糕。比如< /html >标签缺失,最后一个[\s\S] * ?将扩展到字符串末尾,此时没有找到< /html >标签,正则表达式依次向前搜索[\s\S] * ?并记住回溯的位置以便后续使用。 正则表达式尝试扩展到倒数第二个 [ \s\S] * ? 用它匹配 < /body >,匹配成功后,继续向后匹配 ,直到< /html > 失败,然后回溯到倒数第三 [\s\S] * ?, 依次类推。

嵌套量词与回溯失控

所谓的嵌套量词需要额外关注且小心使用,以确保不会引发潜在的回溯失控。嵌套量词是指量词出现在一个自身重复量词修饰的组中。
嵌套量词并不会造成性能危害,然而它很容易在尝试匹配字符串的过程中,在内部变量和外部变量之间产生一大堆文本拆解的路径。

更多提高正则表达式效率的方法
何时不使用正则表达式

当自己小心使用时,正则表达式速度非常快。如果只是搜索字面字符串时,可能就要注意:

// 例如 检查一个字符串是否以分号结尾
endWithSemicolon = /;$/.test(str);

浏览器正则引擎不能意识匹配字符串末尾。它们所做的是逐个测试整个字符串。每当找到一个分号,正则表达式就移动到下一个标记($),检查它是否匹配字符串的末尾。如果不匹配,他会继续搜索匹配项,直到搜索完整个匹配字符串。
在这中情况下,使用简单地检查最后一个字符是否为分号:

endWithSemicolon = str.charAt(str.length - 1) == ";"
使用正则表达式去首尾空白
if(!String.prototype.trim){
    String.prototype.trim = function() {
        return this.replace(/^\s+/,"").replace(/\s\s*$/,"");
    }
}
小结

通过对以上正则的了解

上一篇下一篇

猜你喜欢

热点阅读