JavaScript中的动态与内联RegExp性能

Pio*_*oul 33 javascript regex performance

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

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

RegExp('(?:^| )foo(?: |$)').test(node.className);
Run Code Online (Sandbox Code Playgroud)

/(?:^| )foo(?: |$)/.test(node.className);
Run Code Online (Sandbox Code Playgroud)

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

这些案例被称为"动态正则表达式"和"内联正则表达式".

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

Ale*_*mov 28

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

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

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

  • 随着文字语法- 一次初始代码解析和编译过程
  • 使用RegExp()语法 - 每次创建新对象

例如,请参阅Stoyan Stefanov的JavaScript Patterns一书:

正则表达式文字和构造函数之间的另一个区别是文本在分析时只创建一个对象一次.如果在循环中创建相同的正则表达式,则将返回先前创建的对象及其第一次已设置的所有属性(例如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"
Run Code Online (Sandbox Code Playgroud)

在ES5中此行为已更改,文字也会创建新对象.在许多浏览器环境中,该行为也得到了纠正,因此不能依赖它.

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

false
bar
Run Code Online (Sandbox Code Playgroud)

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

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

(我更惊讶的是inlineRegExp和storedRegExp有不同的结果.)

storedRegExp大约5 -跨浏览器的速度快于20%的百分比inlineRegExp,因为没有创建(和垃圾收集)的新的开销,RegExp每一次的对象.

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

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

  1. 它更短,并且不会强迫您根据类类构造函数进行思考.
  2. 使用RegExp()构造函数时,还需要转义引号和双转义反斜杠.它使正则表达式难以阅读和理解,因为它们的性质更加难以理解.

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

  • 感谢这个不错的更新!我也喜欢这个结论,尽管我很想说“随心所欲地创建正则表达式、文字或构造函数,并在要重用时将其缓存”。换句话说,不要依赖 ES5 以下 envs 中的行为差异,并继续在上面的 envs 中适当缓存:) (2认同)

Arj*_*jan 9

性能的差异与所使用的语法无关,部分与所使用的语法相关:in /pattern/RegExp(/pattern/)(其中你没有测试后者)正则表达式只编译一次,但是RegExp('pattern')表达式是在每个语法上编译的用法.看亚历山大的答案,这应该是今天接受的答案.

除了上述之外,在您的测试中inlineRegExp,storedRegExp您正在查看在解析源代码文本时初始化一次的代码,而dynamicRegExp为每次调用方法创建正则表达式.请注意,实际测试运行r = dynamicRegExp(element)多次,而准备代码只运行一次.

根据另一个jsPerf,以下内容给出了相同的结果:

var reContains = /(?:^| )foo(?: |$)/;
Run Code Online (Sandbox Code Playgroud)

...和

var reContains = RegExp('(?:^| )foo(?: |$)'); 
Run Code Online (Sandbox Code Playgroud)

......当两者​​都使用时

function storedRegExp(node) {
  return reContains.test(node.className);
}
Run Code Online (Sandbox Code Playgroud)

当然,RegExp('(?:^| )foo(?: |$)')可能首先将源代码解析为a String,然后进入a RegExp,但我怀疑它本身会慢两倍.但是,以下将为每个方法调用RegExp(..) 一次又一次地创建一个新的:

function dynamicRegExp(node) {
  return RegExp('(?:^| )foo(?: |$)').test(node.className);
}
Run Code Online (Sandbox Code Playgroud)

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

(我更感到惊讶inlineRegExp并且storedRegExp有不同的结果.亚历山大的回答也解释了这一点.)


dld*_*dnh 6

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