为什么这个JavaScript正则表达式在浏览器中崩溃了?

时间:2015-06-10 15:18:31

标签: javascript regex browser

据我所知,{I}知道[ab](a|b)在尝试匹配一组字符时应该是有效的。现在,看看两个正则表达式:

/^(\s|\u00A0)+|(\s|\u00A0)+$/g
/^[\s\u00A0]+|[\s\u00A0]+$/g

它们应该匹配字符串开头和结尾的空格(有关正则表达式本身的更多信息,请参阅Polyfill here部分)。当使用方括号时,一切都运行良好,但是当你切换到括号时,即使是最简单的字符串也会导致浏览器无限期地运行。这种情况发生在最新的Chrome和Firefox上。

This jsfiddle证明了这一点:

a ="a                                                               b";

// Doesn't work
// alert(a.replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g,''));
// Works
alert(a.replace(/^[\s\u00A0]+|[\s\u00A0]+$/g,''));

这是一个疯狂的怪癖与浏览器的正则表达式引擎的实现或还有其他关于正则表达式的算法导致这个?

1 个答案:

答案 0 :(得分:7)

正如here所述,您所看到的问题被称为灾难性回溯。

首先,让我简化并澄清您的测试用例:

a = Array(30).join("\u00a0") + "b";  // A string with 30 consecutive \u00a0
s = Date.now();
t = a.replace(/^(\s|\u00A0)+$/g, '');
console.log(Date.now()-s, a.length);

正在发生的事情是表达式的第二部分:^(\s|\u00A0)+$。请注意,\s匹配许多空白字符,包括\u00A0本身。这意味着\s\u00A0都会匹配30个\u00A0字符中的每一个。

因此,如果您尝试将字符串与/(\s|\u00A0)+/匹配,您会发现30个字符的空格模式的每个2^30个不同组合都会产生匹配。当正则表达式匹配器匹配前30个字符时,它将尝试匹配字符串结尾($)并失败,因此它会回溯并最终尝试所有2^30组合。

您的原始字符串(在jsfiddle中,其中stackflow中的一个已经“规范化”到所有空格)是a \u00a0 \u00a0 ... \u00a0 b,大约有30个\u00a0个字符,因此浏览器大约需要{{1}努力完成。它不会挂起浏览器,但需要几分钟才能完成。