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

Why is String.replace() with lambda slower than a while-loop repeatedly calling RegExp.exec()?

本文关键字:RegExp 调用 exec while 循环 lambda String 为什么 replace      更新时间:2023-09-26

一个问题:

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

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

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

两个解决方案:

如此:

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]];
});

这也可以,但我对速度的看法是错误的;在Chrome中,这是令人惊讶的(~50%,上次我检查)慢!

三个问题:

  1. 为什么这个过程在Chrome中看起来更慢,而(例如)在Firefox中更快?
  2. 是否有机会replace()方法将比while()循环给定更大的字符串或数组更快?如果不是,它在高尔夫代码之外的好处是什么?
  3. 是否有一种方法可以优化这一过程,使其既高效又像第二种方法那样省事?

我欢迎任何关于这些过程背后发生的事情的见解。

[>Fo(u)r记录:我很高兴被称为我使用的词'lambda'和/或'functional'。我还在学习这些概念,所以不要以为我完全知道我在说什么,如果我在这里误用了术语,请随时纠正我。

为什么这个过程在Chrome中看起来更慢,而(例如)在Firefox中更快?

因为它必须调用(非本机)函数,这是昂贵的。Firefox的引擎也许可以通过识别和内联查找来优化这个问题。

是否有机会,replace()方法将比while()循环给定更大的字符串或数组更快?

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

如果没有,它在Code Golf之外的好处是什么?

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

是否有一种方法可以优化这个过程,使它既高效又像第二种方法那样省事?

您不需要remainder变量。rgx有一个lastIndex属性,它会自动将匹配推进到str

使用exec()while循环略慢,因为您在非全局正则表达式上使用exec()时正在做额外的工作(substring)。如果你需要循环遍历所有匹配,你应该在全局正则表达式上使用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()返回值的情况。示例1 。示例2

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

我认为exec()用于替代之外的目的(除了非常特殊的情况,例如this)。如果可能的话,应该替换replace()

优化只有在实际输入的性能严重下降时才有必要。我没有任何优化要展示,因为两个唯一可能的选项已经分析过了,两个不同浏览器之间的性能矛盾。这在将来可能会改变,但是现在,您可以选择跨浏览器性能最差的浏览器来使用。