在JavaScript中读取数组的' length '属性是否真的是一个昂贵的操作
Is reading the `length` property of an array really that expensive an operation in 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引擎。
- 在JavaScript中缓存数组的
length
属性是否有任何优势?在对象的属性上读取局部变量是否涉及更多? - 是
length
属性简单地改变创建和shift()
和pop()
类型的方法,不返回一个新的数组,否则简单地存储为一个整数?
嗯,我本来会说这是昂贵的,但后来我写了一个小测试@ jsperf.com,令我惊讶的是,使用i<array.length
实际上在Chrome中更快,在FF(4)中没关系。
我的怀疑是长度存储为整数(Uint32)。来自ecma规范(262版5,第121页):
每个Array对象都有一个长度属性,其值总是小于232的非负整数。长度属性的值为的名称在数字上大于每个名称为数组的属性指数;数组的属性对象被创建或更改时,其他根据需要调整属性保持这个不变量。具体来说,每当属性是添加了名称为数组索引的对象,长度属性被改变,如果必要的,要比该数组索引的数值;和只要长度属性是更改了,所有名称为的属性数组索引,其值为小于新的长度自动删除。这个约束类的自身属性数组对象,不受长度或数组索引属性可以从它的原型继承
唷!我不知道我是否习惯了这样的语言。
最后,我们的老浏览器总是落后的。在IE(9,8,7)中缓存长度确实更快。我说,这是不使用IE的众多原因之一。
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_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();
那么,在回答你的问题时:
- 似乎没有任何优势在Chrome(或其他浏览器使用V8)缓存一个数组的
length
属性(除非你做一些奇怪的事情,将迫使它是slow
(我不确定这些条件是什么)-已经说过,我很可能会继续缓存length
,直到我有机会通过所有操作系统浏览器实现;) - 在对对象进行任何操作后,
length
属性似乎被改变了。
另一方面,似乎一个"空"数组实际上被分配为有4个元素:
// Number of element slots to pre-allocate for an empty array.
static const int kPreallocatedArrayElements = 4;
我不确定一旦超出边界,数组会增长多少元素-我没有深入挖掘:)
另一组性能测试。循环是在一个包含一百万个随机数的数组上进行的,其中有一个空循环。
在Chrome中,缓存和非缓存长度的循环时钟几乎相同的时间,所以我猜这是V8优化缓存长度。
在Safari和Firefox中,缓存的长度始终比未缓存的版本快2倍。
本文通过向IRHydra请求生成的代码来研究V8和Chrome中的自动缓存:
圣诞怪杰是如何偷走数组的。Vyacheslav Egorov访问长度
他发现在某些条件下手动缓存.length
实际上增加了开销而不是提高了性能!
但无论如何,这种微优化不太可能为您的用户获得任何明显的收益。为了他们的利益,也为了你自己的利益,你应该把重点放在易于阅读的代码上,并在代码中使用良好的数据结构和算法!
避免过早优化:专注于优雅的代码,直到出现性能问题。只有这样,才能通过分析找出瓶颈,然后优化代码的那部分。
提醒一下:
在一些浏览器上(我注意到在Safari, IE和Opera中),您可以通过缓存for循环声明中的长度来提高速度:
var j;
for (var i = 0, len = arr.length; i < len; i++) {
j = arr[i];
}
我编辑了@KooiInc上面的jsperf测试来添加这种情况。
注意不要假设所有可迭代集合都是如此。例如,缓存HTMLCollection的长度在Chrome(版本41)中快65%,在Firefox(版本36)中快35%。
http://jsperf.com/array-length-in-loop-dom- fluxxor向一个flux实例添加一组以上的操作
- jQuery-将列表项的一个元素移动到同一项的另一个元素中+对我的列表中的每个项执行此操作
- 选中复选框的Jquery/Javascript问题使用输入框操作将行从一个表添加到另一个表
- 一个jsp中有两个操作URL
- 使用react router/react router redux将特定的redux操作绑定到一个路由
- 响应应包含一个对象,但得到的却是GET操作的数组
- 制作一个高阶组件,用TypeScript实现反应中继和反应路由器的交互操作
- 当我的表单中已经有一个操作时添加JavaScript
- javascript下载一个文件并执行提交操作
- 为主题选项(部分)创建一个ajax处理的保存操作
- 表单操作 一个外部 JavaScript 函数
- 两个点击操作一个按钮
- 一个表单需要两个操作(一个本地,一个远程)
- 操作一个JS对象
- 两个jquery提交,每个操作一个
- Ajax -操作一个正确的数组以传递给PHP
- 操作一个包含文本的DOM节点和innerHTML以外的其他节点
- 是否可以从JS暂停/恢复/操作一个swift对象?
- Jquery多个操作一个if
- 根据其他类的css样式来操作一个类的css风格