为什么String.replace()的lambda慢于while循环重复调用RegExp.exec()?

时间:2013-03-17 20:01:37

标签: javascript regex performance optimization lambda

一个问题:

我想处理一个字符串(str),以便任何带括号的数字(由rgx匹配)替换为从数组中的适当位置取得的值(sub):

var rgx = /\((\d+)\)/,
    str = "this (0) a (1) sentence",
    sub = [
            "is",
            "test"
        ],
    result;

鉴于上面声明的变量,result应该是'这是一个测试句'。

两个解决方案:

This works

var mch,
    parsed = '',
    remainder = str;
while (mch = rgx.exec(remainder)) { // Not JSLint approved.
    parsed += remainder.substring(0, mch.index) + sub[mch[1]];
    remainder = remainder.substring(mch.index + mch[0].length);
}
result = (parsed) ? parsed + remainder : str;

但我认为以下代码会更快。它具有更少的变量,更简洁,并使用匿名函数表达式(或 lambda ):

result = str.replace(rgx, function() {
    return sub[arguments[1]];
});

This works too,但我的速度错了; in Chrome it's surprisingly (~50%, last time I checked) slower

...

三个问题:

  1. 为什么这个过程在Chrome中显得较慢,(例如)在Firefox中速度更快?
  2. 与给定较大字符串或数组的replace()循环相比,while()方法是否有可能更快?如果没有,Code Golf之外的好处是什么?
  3. 是否有一种优化此过程的方法,使其更有效率,并且与功能性第二种方法一样无忧无虑?
  4. 我欢迎任何有关这些流程背后发生的事情的见解。

    ...

    [ Fo(u)r记录:我很高兴在使用'lambda'和/或'functional'这个词时被叫出来。我还在学习这些概念,所以不要以为我确切知道我在说什么,如果我在这里误用这些条款,请随时纠正我。]

2 个答案:

答案 0 :(得分:3)

  

为什么这个过程在Chrome中显得较慢,(例如)在Firefox中速度更快?

因为它必须调用(非本机)函数,这是昂贵的。 Firefox的引擎可能能够通过识别和inlining the lookup来优化它。

  

与给定较大字符串或数组的while()循环相比,replace()方法是否有可能更快?

是的,它必须减少字符串连接和分配,并且 - 正如您所说 - 初始化的变量较少。然而,你只能测试它来证明我的假设(并且还可以查看http://jsperf.com/match-and-substitute/4的其他片段 - 例如,你可以看到Opera优化了不使用arguments的lambda-replace2。) p>

  

如果没有,Code Golf以外有什么好处?

我不认为代码高尔夫是正确的术语。软件质量是关于可读性和可理解性的,在其术语中,功能代码的简洁优雅(这是主观的)是使用这种方法的原因(实际上我是'我从未见过用execsubstring替换并重新连接。

  

有没有办法优化这个过程,使其更有效率,并且与功能性第二种方法一样无忧无虑?

您不需要remainder变量。 rgxlastIndex property,会自动通过str推进匹配。

答案 1 :(得分:2)

while循环exec()稍微慢一点,因为你在非全局使用substring时正在做额外的工作(exec())正则表达式。如果需要遍历所有匹配项,则应在全局正则表达式(while标志启用)上使用g循环;这样,你就可以避免额外的工作修剪字符串的处理部分。

var rgR = /\((\d+)\)/g;
var mch,
    result = '',
    lastAppend = 0;

while ((mch = rgR.exec(str)) !== null) {
    result += str.substring(lastAppend, mch.index) + sub[mch[1]];
    lastAppend = rgR.lastIndex;
}
result += str.substring(lastAppend);

此因素不会影响不同浏览器之间的性能差异。

性能差异似乎来自浏览器的实现。由于对实施的不熟悉,我无法回答差异的来源。

就权力而言,exec()replace()拥有相同的权力。这包括您不使用replace()返回值的情况。 Example 1Example 2

replace()方法比使用while exec()循环更具可读性(意图更清晰)如果您正在使用函数返回的值(即你在匿名函数中做真正的替换)。您也不必自己重建替换的字符串。这是replace优于exec()的地方。 (我希望这能回答问题2的第二部分)。

我认为exec()用于替换以外的目的(除this之类的特殊情况外)。如果可能,应使用replace()进行替换。

如果性能在实际输入上严重降低,则只需要优化。我没有任何优化显示,因为已经分析了2个可能的选项,2个不同浏览器之间的性能相矛盾。这可能会在未来发生变化,但就目前而言,您可以选择浏览器中性能最差的一个。