在JavaScript中读取数组的`length`属性真是那么昂贵吗?

时间:2011-04-22 06:36:04

标签: javascript arrays

我总是假设在JavaScript中缓存数组的长度是一个好主意(特别是在for循环的条件下),因为计算数组长度的代价很高。

实施例

for (var i = 0; i < arr.length; i++) { }

// vs

for (var i = 0, arrLength = arr.length; i < arrLength; i++) { }

但是,我认为length属性可能仅在创建和更改数组时更新。因此,阅读它不应该是一个操作太昂贵而不是读取存储在变量中的操作(与其他语言中可能需要在内存中寻找某些内容的其他方法相反,例如strlen()在C)。

我有两个问题。我也对它是如何工作感兴趣,所以请不要用过早优化棒来打击我。

假设浏览器中的JavaScript引擎。

  1. 在JavaScript中缓存数组的length属性有什么好处吗?是否有更多的参与在对象的属性上读取局部变量?
  2. length属性是否仅在创建时以及shift()pop()类型方法上更改,这些方法不返回新数组,否则只是存储为整数?

6 个答案:

答案 0 :(得分:47)

好吧,我会说它很贵,但后来我写了一个小测试@ jsperf.com,令我惊讶的是使用i<array.length实际上在Chrome中更快,在FF(4)它没有没关系

我怀疑是长度存储为整数(Uint32)。来自ECMA-specs(262 ed.5,page 121):

  

每个Array对象都有一个   length属性,其值始终是小于2 32 的非负整数。 length属性的值是   在数字上大于名称   名称为数组的每个属性   指数;每当一个数组的属性   对象是创建或更改的,其他   根据需要调整属性   保持这种不变性。   具体来说,无论何时财产   添加了谁的名字是一个数组索引,   如果,则更改length属性   必要的,比一个更多   该数组索引的数值;和   每当长度属性是   改变了,每个属性的名字都是   一个数组索引,其值不是   小于新的长度   自动删除。这个约束   仅适用于自己的属性   数组对象并且不受影响   长度或数组索引属性   可以从其原型继承

唷!我不知道我是否习惯了这种语言......

最后,我们总是让我们的老人落后于浏览器。在IE(9,8,7)中,缓存的长度确实更快。我说,不使用IE的更多原因之一。

答案 1 :(得分:26)

TL; DR:

从我可以收集到的内容来看,似乎就像内部缓存数组的长度一样(至少在V8中)..

(详情?继续阅读:))

所以,这个问题已经在我的脑海中爆发了几次,我决定找到问题的根源(至少在一个实现中)。

挖掘V8源代码产生了JSArray类。

// The JSArray describes JavaScript Arrays
//  Such an array can be in one of two modes:
//    - fast, backing storage is a FixedArray and length <= elements.length();
//       Please note: push and pop can be used to grow and shrink the array.
//    - slow, backing storage is a HashTable with numbers as keys.

我假设数组元素的类型决定它是快还是慢。我在set_has_fast_elementsset_bit_field2(bit_field2() | (1 << kHasFastElements)))设置了一个标志,这是我想要绘制挖掘线的地方,因为我在查看Google代码并且没有本地源代码

现在,似乎 任何时间任何操作都在数组上完成(这是JSObject的子类,调用NormalizeElements(),执行以下操作:

// Compute the effective length.
  int length = IsJSArray() ?
      Smi::cast(JSArray::cast(this)->length())->value() :
      array->length();

所以,在回答你的问题时:

  1. Chrome(或其他使用V8的浏览器)似乎没有任何优势来缓存数组的length属性(除非你做了一些奇怪的事情会强制它slow 1}}(我不确定那些条件是什么) - 说过,我很可能会继续缓存length,直到我有机会通过所有操作系统浏览器实现;)
  2. 对象的任何操作后,length属性似乎已被更改。
  3. 修改

    另外,似乎“空”数组实际上分配了4个元素:

    // Number of element slots to pre-allocate for an empty array.
    static const int kPreallocatedArrayElements = 4;
    

    我不确定一旦超出界限,数组会增加多少元素 - 我没有深入挖掘

答案 2 :(得分:12)

另一组效果tests。循环在一百万个随机数的数组上完成,并带有一个空循环。

在Chrome中,缓存和非缓存长度的循环时间几乎相同,因此我猜测缓存长度是V8优化。

在Safari和Firefox中,缓存长度始终比非缓存版本快2倍。

答案 3 :(得分:5)

本文通过询问IRHydra生成的代码来调查V8和Chrome中的自动缓存:

  Vyacheslav Egorov

How the Grinch stole array.length access

他发现在某些条件下手动缓存.length实际增加的开销而不是提高性能!

但无论如何,这种微优化不太可能为您的用户带来任何显着的收益。为了他们的利益,为您的利益而重点关注明确阅读的代码,并在代码中使用良好的数据结构和算法!

Avoid premature optimisation:专注于优雅的代码,直到出现性能问题。只有这样,通过分析找出瓶颈,然后优化代码的那部分

答案 4 :(得分:4)

请注意:

在某些浏览器上(我在Safari,IE和Opera中注意到它),您可以通过缓存for循环声明中的长度来提高速度:

var j;
for (var i = 0, len = arr.length; i < len; i++) {
  j = arr[i];
}

我在上面编辑了@ KooiInc的jsperf测试add this case

答案 5 :(得分:2)

注意不要假设所有可迭代集合都是如此。例如,缓存HTMLCollection的长度在Chrome(版本41)中快65%,在Firefox(版本36)中快35%。

http://jsperf.com/array-length-in-loop-dom