在 JavaScript 中读取数组的“长度”属性真的那么昂贵吗?

我总是假设用 JavaScript 缓存数组的长度是一个好主意(特别是在 for循环的情况下) ,因为计算数组的长度非常昂贵。

例子

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


// vs


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

但是,我认为可能 length属性只在创建和更改数组时更新。因此,与读取存储在变量中的数据相比,读取数据不应该太昂贵(其他语言中的方法可能需要在内存中查找某些数据的结尾,例如 C 语言中的 strlen())。

我有两个问题。我也很感兴趣这是如何工作的,所以请不要打我与 过早的优化棒。

假设浏览器中有 JavaScript 引擎。

  1. 在 JavaScript 中缓存数组的 length属性有什么好处吗?在读取对象属性的局部变量时是否涉及更多内容?
  2. length属性在创建时以及在 shift()pop()类型方法不返回新数组或仅仅存储为整数时是否发生了改变?
15411 次浏览

好吧,我本来想说它很贵,但是后来我写了一个小测试@JsPerf.com,令我惊讶的是使用 i<array.length实际上在 Chrome 中更快,而在 FF (4)中这并不重要。

我怀疑长度是以整数形式存储的(Uint32):

每个 Array 对象都有一个 长度属性,其值始终是小于232的非负整数。Length 属性的值为 在数字上大于 名称为数组的每个属性 每当 Array 的属性 对象创建或更改,则其他 物业按需要调整 来保持这个不变量。 具体来说,只要属性是 名称为数组索引的, 如果更改了 length 属性,则 必要的,是一个超过 该数组索引的数值; 以及 每当 length 属性为 名称为 值不是 比新的长度小 自动删除。此约束 只适用于自己的属性 数组对象,不受 长度或数组索引属性 可以从它的原型继承

呼! 我不知道我能不能习惯这种语言..。

最后,我们总是有我们的好老落后浏览器。在 IE (9,8,7)中缓存长度确实更快。我说,这是不使用 IE 的众多理由之一。

另一组性能 测试。循环是在一个包含百万个随机数的数组上完成的,其中包含一个空循环。

在 Chrome 中,具有缓存长度和非缓存长度的循环时钟几乎相同,所以我猜测这是一个缓存长度的 V8引擎优化。

在 Safari 和 Firefox 中,缓存的长度一直比非缓存的版本快2倍左右。

译者:

据我所知,它的 看起来就像数组的长度一样是在内部缓存的(至少在 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_elements(set_bit_field2(bit_field2() | (1 << kHasFastElements))) ,这是我认为我会绘制的挖掘线,因为我在谷歌代码,没有在本地的源代码。

现在,对数组执行 任何时间 任何操作(这是 JSObject的子类,对 NormalizeElements()进行调用,执行以下操作:

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

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

  1. Chrome 浏览器(或者其他使用 V8的浏览器)似乎没有任何优势来缓存数组的 length属性(除非你正在做一些奇怪的事情,迫使它成为 slow(我不确定那些条件是什么)——话虽如此,我最有可能继续缓存 length,直到我有机会通过 所有操作系统浏览器的实现;)
  2. 在对对象进行 任何操作之后,似乎改变了 length属性。

编辑:

值得注意的是,一个“空”数组实际上被分配为4个元素:

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

我不确定一旦超出了界限,数组会增加多少个元素——我没有深入挖掘 那个:)

提醒你一下:

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

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

我将上面@KooiInc 的 jsperf 测试编辑为 加上这个案子

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

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