为什么第三个选项比正则表达式更好?

5
我认为正则表达式相当快,第三个选项很令人困惑。你怎么看? http://jqfundamentals.com/book/ch09s12.html
// old way
if (type == 'foo' || type == 'bar') { ... }

// better
if (/^(foo|bar)$/.test(type)) { ... }

// object literal lookup 
if (({ foo : 1, bar : 1 })[type]) { ... }

除非在用户等待某些操作时需要执行数十万次,否则这并不重要(我也不认为其他选项在跨平台实现方面更好)。选择可读性和可维护性(第一个选项)。 - T.J. Crowder
她真的是说第三个选项最好吗?看起来她只是在告诉选项。 - Sinan
7个回答

6

我谦虚地不同意Rebecca Murphey,支持第一个选项的简单性。

我认为正则表达式非常快
机器码更快,但我们不使用它。

第三个选项很困惑
只有当你不熟悉这个技巧时才会感到困惑。(对于不习惯使用正则表达式比较两个字符串的人来说,第二个选项会更加困惑。)


5
我刚刚做了一个初步的基准测试,但老实说我不确定她是如何得出那些结果的... http://jsbin.com/uzuxi4/2/edit 正则表达式似乎是最好的扩展方式,但第一个在所有现代浏览器中都是最快的。最后一个非常慢。虽然我理解这三个之间的复杂性理论,但在实践中,它似乎并不正确。
更不用说第一个还拥有最好的可读性,它也似乎是最快的。我甚至嵌套循环来利用任何浏览器对字面表或常量的缓存(但无济于事)。

编辑: 看起来当一个对象被明确地创建时,她是正确的,然而:http://jsbin.com/uzuxi4/4/edit

function __hash() {
  ...

  var type = 'bar';
  var testobj = { foo : 1, bar : 1 };
  var c = 0;
  for (i = 0; i < 1000; i++) {
    if (testobj[type]) {
      for (j = 0; j < 10000; j++) {
          if (testobj[type]) { c++; }
      }
    }
  }

  ...
}

我们注意到,一旦对象具有内部引用,寻道时间就会降至约500毫秒,这可能是一个平稳期。在实践中,对象键查找可能是处理更大数据集的最佳选择,但我并不认为它是日常使用的可行选项。

1
她忽略了她所做的事情的成本——在任何现代引擎中,对对象的[]访问都不会被缓存,当然你还必须分配该对象(然后稍后支付垃圾回收的成本)。 - olliej

2
  1. 第一个选项涉及到潜在的两个字符串比较。
  2. 第二个选项涉及每次解析。
  3. 第三个选项对字符串进行简单的哈希,然后进行哈希表查找,在这种情况下,它是最高效的,需要完成的工作量最少。

随着添加更多的备选字符串,第三个选项比其他两个选项更具扩展性,因为前两个选项是O(n),而第三个选项在平均情况下是O(1)。

如果我们想讨论哪个选项更美观/更易维护,那就是完全不同的对话了。


第三个选项是O(n),但首先你必须创建哈希表。(当然,除非你想手动缓存它) - Nikita Rybak
当然,这也涉及到完全不必要的内存分配,并且仍然需要进行字符串比较以获取真实情况(哈希值可以足以满足假情况,但不是真实情况)。对于维护者来说,这也很难阅读,任何性能优势几乎肯定是依赖于实现的,并且在除了边缘情况之外不值得麻烦。 - T.J. Crowder
是的,但假设哈希值只创建一次,然后再使用一个好的JavaScript解释器,因为这些值是常量。尽管我同意T.J.Crowder的观点,认为这种写法更难读懂。 - Michael Goldshteyn
@Michael:那么,不是地球上最流行的浏览器中的那一个。 - T.J. Crowder
@Michael:我们都是抱着希望的,没错。 :-) - T.J. Crowder
最有效率的是什么?我创建了这个测试,发现字符串是最快的... http://jsperf.com/string-vs-regex/3 - Andrew

1

第一种情况应该使用 === 来避免任何类型强制转换,但是根据您需要检查的替代方案数量,它可能会变成 O(N),但是根据您的代码,大多数JS引擎将能够进行简单的指针检查以进行比较。

在第二种情况下,您使用了RegExp,虽然RegExp非常快,但它们 tend to be slower for simple equality decisions than more direct equality comparisons. 您的简单字符串比较可能是现代JS引擎中的指针比较,但如果您使用正则表达式,则必须读取每个字符。

第三种情况更棘手-如果您确实有很多要检查的值,则可能会更快,特别是如果您缓存对象而不是重复创建它,因为它只是哈希查找-查找的确切性能取决于引擎。

我怀疑switch语句会打败对象文字情况。

出于好奇,我进行了测试(您可以在此处看到),最快的方法(至少在webkit nightly中)似乎是switch语句,其次是if,其次是对象,最后是regexp。


+1,我真的不明白她怎么能把第2或第3种情况称为“最佳实践”,我真的希望我们不会看到每个人都用regexp进行简单比较。 - David Titarenco

1

我想在这里发表一下意见,提醒大家这是一本开源书籍,有很多人做出了贡献!讨论的部分确实是由社区成员提供的内容。如果您有改进该部分的建议,请在存储库上打开问题,或更好地,派生存储库并向我发送拉取请求 :)

话虽如此,我刚刚设置了一个 jsPerf 测试(http://jsperf.com/string-tests),至少在 Chrome 中,结果与书中所说的相反。我已经在书上开了一个问题,并将尝试在不久的将来解决这个问题。

最后,两件事:

  • 我想echo另一个评论者说的话:性能优化很有趣,虽然有些确实很重要,但很多并不重要。重要的是要保持对这种东西产生多大或多小影响的透彻认识。
  • 我也想echo那位评论者的话,基本上就是说可读性在于观察者的眼睛。对某个人来说困惑的东西可能对另一个人来说非常清晰。我确实认为我们应该追求可读性,但我认为有一个平衡点。阅读一些起初让我感到有些困惑的代码,开启了我对很多优秀技术的认识;如果当时完全是为了让像我这样的新手理解而编写的话,我会很讨厌的。

1
我在看到你的帖子之前创建了这个测试,实际上我对结果感到震惊。http://jsperf.com/string-vs-regex/3 - Andrew

0
对象字面量查找使用哈希查找进行优化,只需要一个逻辑检查而不是n个。在更长的列表中,您也不必重复“type ==”无数次。

但是你必须分配并创建对象。 - olliej
是的,但是如果有一个良好实现的JS解释器,就不必创建多个。我并不是说它一定被很好地实现了。 - Peter DeWeese
你不能每次都分配对象,因为对象可能会逃逸。例如: - olliej
按下回车键在注释中提交是为什么? - olliej
Object.prototype.defineGetter("wibble", function(){this.x = old.x+1; old=this;}); 然后如果 ({....}[someUnknownValue]) { ... } 可以逃脱,你就必须创建一个新对象。JavaScript 中的许多缓慢是由于某人可能会做一些愚蠢的事情所导致的。 - olliej

0

为了简单和可读性,第一种方法总是胜出的。除非它在一个频繁执行的循环中使用,否则速度可能不会那么快,但是谁在意呢。

好的编译器应该能够优化掉这样的东西。


他们正在优化什么?这是JavaScript中令人惊讶的难以优化的事情。 - olliej
当你比较两个项目时,你可以争论第一个的可读性更好,但如果你有4个或更多的项目,它将开始超过一行,可读性就成了一个问题。 - Toby
@olliej:在像JavaScript这样的动态语言中,很多东西都很难进行优化,但是if (type == 'foo' || type == 'bar')不是其中之一 :-) - T.J. Crowder
@T. J. Crowder:您要进行优化的是哪些部分?您必须执行检查(请注意,我假设字符串已经被内部化,因此通常可以通过指针比较来实现)。 - olliej
我上了一门研究生级别的计算机科学课程,在这门课中,我对一个C++程序进行了极致优化。然而,我的优化程序比编译器使用“标准”代码的表现还要差。班上的每个人都有同样的经历。算法设计比苦恼于一两行代码更重要。C++不是Javascript,但你的代码越简单,机器和人就越容易处理。 - Telavian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接