这个RegEx函数中的内存问题是什么?
What is the memory issue in this RegEx function
我正在尝试抓取电子邮件地址的网页。我几乎有它的工作,但似乎有某种巨大的内存错误,使页面冻结当我的脚本加载。
这是我的:
var bodyText = document.body.textContent.replace(/'n/g, " ").split(' '); // Location to pull our text from. In this case it's the whole body
var r = new RegExp("[a-z0-9!#$%&'*+'/=?^_`{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", 'i');
function validateEmail(string) {
return r.test(string);
}
var domains = [];
var domain;
for (var i = 0; i < bodyText.length; i++){
domain = bodyText[i].toString();
if (validateEmail(domain)) {
domains.push(domain);
}
}
我唯一能想到的是,我使用的电子邮件验证函数是一个32步表达式,我运行它的页面返回超过3000个部分,但我觉得这应该是可能的。
下面是一个重现错误的脚本:var str = "help.yahoo.com/us/tutorials/cg/mail/cg_addressguard2.html";
var r = new RegExp("[a-z0-9!#$%&'*+'/=?^_{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", 'i');
console.log("before:"+(new Date()));
console.log(r.test(str));
console.log("after:"+(new Date()));`
我能做些什么来克服内存问题?
stribizhev在评论中指出了解决方案:在RegExp literal语法中指定正则表达式。另一个解决方案,如sln的注释所示,是正确转义字符串字面值中的'
。
我不会在这个答案中解决用regex验证/匹配电子邮件地址的正确正则表达式是什么,因为它已经被重复了很多次。
为了演示导致问题的原因,让我们将传递给RegExp构造函数的字符串打印到控制台。你注意到有些'
不见了吗?
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])
^ ^ ^ ^
上面的字符串是RegExp构造函数看到并编译的。
/
只需要在RegExp字面量中转义(因为RegExp字面量由/
分隔),并且不需要在传递给RegExp构造器的字符串中转义,因此省略不会引起任何问题。下面是等效的示例,展示了如何编写一个正则表达式来匹配
/
与RegExp字面值和RegExp构造函数:/'//; new RegExp("/");
然而,由于
'.
中的'
在字符串中没有被正确转义,所以它不匹配文字.
,它允许匹配任何字符(除了行分隔符)。因此,作为完美的解决方案,正则表达式中的这些部分遭受了灾难性的回溯:
(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)* (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+
由于
.
可以匹配任何字符,因此上面的片段退化为经典的灾难性回溯模式(A*)*
。通过减少正则表达式对其严格子集的能力,您可以更清楚地看到问题:(?:a[a]+)* (?:[a](?:[a]*[a])?a)+
这是RegExp字面量的解决方案,它与问题中的字符串字面量中指定的相同。您已经正确地完成了RegExp文字的转义,但是在RegExp构造函数中使用它:
var r = /[a-z0-9!#$%&'*+'/=?^_`{|}~-]+(?:'.[a-z0-9!#$%&'*+'/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])/i;
对于等效的RegExp构造函数解:
var r = new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:''.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?''.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])", "i");
不完全是你问题的答案,但你需要做的第一件事是减少你必须用"纠正"的文本部分进行测试的数量。模式。在html示例文件中,您有大约3300个文本字符串要用正则表达式进行测试。请记住,使用正则表达式是有代价的,所以删除无用的文本部分是优先考虑的:
var textParts = document.body.textContent
.split(/'s+/) // see the note
.filter(function(part) {
return part.length > 4 && part.length < 255 && part.indexOf('@') > 1;
});
alert(textParts.join("'n"));
现在您只有~50个文本部分要测试。
注意:如果你想在帐户电子邮件地址中加入双引号,你可以尝试更改:
.split(/'s+/)
.split(/(?=['s"])((?:"[^"'n'']*(?:''.[^"'n'']*)*"[^"'s]*)*)(?:'s+|$)/)
(无担保)
关于你的模式:你的模式中的错误已经被其他答案和注释指出了,但请注意,你可能可以用这个更快地获得相同的结果(相同的匹配):
/'b'w[!#-'*+'/-9=?^-~-]*(?:'.[!#-'*+'/-9=?^-~-]+)*@[a-z0-9]+(?:-[a-z0-9]+)*'.[a-z0-9]+(?:[-.][a-z0-9]+)*'b/i
下面是一个不那么严格的正则表达式的例子。
function getEmails(str) {
var r = /'b[A-Z0-9._%+-]+@[A-Z0-9.-]+'.[A-Z]{2,4}'b/ig;
var emails = [];
var e = null;
var n = 0;
while ((e = r.exec(str)) !== null) {
emails[n++] = e[0];
}
return emails;
}
function emailTest() {
var str = document.getElementsByTagName('body')[0].innerHTML;
var emails = getEmails(str);
document.getElementById('found').innerHTML=emails.join("'n");
}
emailTest();
#found {
color:green;
font-weight:bold;
}
<pre id="email_test">
test@test.test
foo@bar.baz.test
foo@bar.baz.longdomain
foo-bar@foo.bar
foo_bar99@foo.bar
foo@foo@foo.bar
foo$bar#33@test.test
foo+bar-baz%99@someplace.top
</pre>
<pre id="found"></pre>