JavaScript中的动态与内联RegExp性能

时间:2012-03-17 13:27:03

标签: javascript regex performance

我偶然发现了性能测试,说JavaScript中的RegExps不一定很慢:http://jsperf.com/regexp-indexof-perf

有一件事我没有得到:两件案件涉及我认为完全相同的事情:

RegExp('(?:^| )foo(?: |$)').test(node.className);

/(?:^| )foo(?: |$)/.test(node.className);

在我看来,这两行完全相同,第二行是创建一个RegExp对象的某种简写。不过,它比第一个 快两倍

这些案例被称为“动态正则表达式”和“内联正则表达式”。

有人可以帮我理解这两者之间的差异(以及性能差距)吗?

3 个答案:

答案 0 :(得分:24)

如今,这里给出的答案并不完全正确。

从ES5开始,文字语法行为与关于对象创建的RegExp()语法相同:每次代码路径到达表达式时都会创建一个新的RegExp对象正在参加。

因此,现在它们之间唯一的区别就是编译正则表达式的频率

  • 在初始代码解析期间使用文字语法 - 一次 编译
  • 使用RegExp()语法 - 每次创建新对象

例如,请参阅Stoyan Stefanov's JavaScript Patterns book:

  

正则表达式文字与文本之间的另一个区别   构造函数是文字只在一段时间内创建一个对象   解析时间。如果在循环中创建相同的正则表达式,则   以前创建的对象将返回其所有属性   (例如lastIndex)已经从第一次设置。考虑一下   以下示例作为同一对象的说明   两次返回。

function getRE() {
    var re = /[a-z]/;
    re.foo = "bar";
    return re;
}

var reg = getRE(),
    re2 = getRE();

console.log(reg === re2); // true
reg.foo = "baz";
console.log(re2.foo); // "baz"
  

此行为在ES5中已更改,文字也会创建新对象。许多浏览器也纠正了这种行为   环境,所以不能依赖它。

如果您在所有现代浏览器或NodeJS中运行此示例,则会得到以下内容:

false
bar

意味着e 您正在调用getRE()函数时,即使使用文字语法方法,也会创建一个新的RegExp对象。

以上不仅解释了为什么你不应该将RegExp()用于不可变的regexp(今天这是众所周知的性能问题),而且还解释了:

  

(我更感到惊讶的是,inlineRegExp和storedRegExp有所不同   的结果。)

storedRegExp跨浏览器的速度比inlineRegExp快5到20%,因为每次创建(和垃圾回收)新的RegExp对象都没有开销。

<强>结论:
始终使用文字语法创建不可变的regexp,并在重新使用时对其进行缓存。换句话说,不要依赖ES5下的envs中的行为差异,并在上面的envs中继续适当缓存。

为什么是字面语法?与构造函数语法相比,它具有一些优势:

  
      
  1. 它更短,不会强迫你从类的角度思考   构造函数。
  2.   
  3. 使用RegExp()构造函数时,还需要转义引号和双转义反斜杠。它使正则表达式   他们的性质难以阅读和理解更加困难。
  4.   

(来自同一本Stoyan Stefanov's JavaScript Patterns书的免费引用) 因此,除非在编译时不知道正则表达式,否则坚持使用文字语法总是一个好主意。

答案 1 :(得分:9)

性能差异与使用的语法无关部分与所使用的语法相关:/pattern/RegExp(/pattern/)(你没有使用的语法)测试后者)正则表达式只编译一次,但对于RegExp('pattern'),表达式在每次使用时编译。请参阅Alexander's answer,这应该是今天接受的答案。

除了上述内容之外,在inlineRegExpstoredRegExp的测试中,您正在查看在解析源代码文本时初始化的代码,而对于dynamicRegExp常规代码为每次调用方法创建表达式。请注意,actual tests多次执行r = dynamicRegExp(element)之类的操作,而准备代码只运行一次。

以下内容为您提供了相同的结果,according to another jsPerf

var reContains = /(?:^| )foo(?: |$)/;

...和

var reContains = RegExp('(?:^| )foo(?: |$)'); 

...当两者​​都与

一起使用时
function storedRegExp(node) {
  return reContains.test(node.className);
}

当然,RegExp('(?:^| )foo(?: |$)')的源代码可能首先被解析为String,然后被解析为RegExp,但我怀疑它本身会慢两倍。但是,以下内容将为每次方法调用一次又一次地为<{1}} 创建

RegExp(..)

如果在原始测试中你只调用一次方法,那么内联版本的速度将不会快2倍。

(我更感到惊讶的是function dynamicRegExp(node) { return RegExp('(?:^| )foo(?: |$)').test(node.className); } inlineRegExp有不同的结果。Alexander's answer也解释了这一点。)

答案 2 :(得分:6)

在第二种情况下,在解析语言期间创建正则表达式对象,在第一种情况下,RegExp类构造函数必须解析任意字符串。