【问题标题】:Javascript: Is the length method efficient?Javascript:长度方法有效吗?
【发布时间】:2025-12-03 21:35:01
【问题描述】:

我正在做一些 javascript 编码,我想知道长度方法是“预先计算”的,还是被 JS 引擎记住的。

所以,问题是:

如果我经常检查数组长度,并且假设我没有更改它(通过闭包使其不可变),我是否应该预先计算长度方法并将其存储在某个变量中?

谢谢!

【问题讨论】:

  • 内置javascript对象中没有length 方法,但访问对象属性通常比访问局部变量慢。
  • 为什么是@Pointly?我不能在我的代码中寻找每一个改进吗?我就不能好奇吗?
  • 保持好奇心很好,但编写(正如 Erlang 世界的 Joe Armstrong 所说的)“漂亮的代码”也很重要。有时,当 JavaScript 实现引入新的优化以加快使用常见习语编写的代码时,如此小的“优化”可能会适得其反。
  • 所以,如果数组在每次调用 .lenth 方法时计算它的长度(我不知道,这就是我在这里问的原因)并且我在其中存储了 1000000 个对象,那就是“小”性能损失?我同意你的可读性,但也不想杀死我的用户浏览器。

标签: javascript arrays immutability


【解决方案1】:

一如既往,答案是“视情况而定”。

Let's test native arrays with a million-element array:

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

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

http://josh3736.net/images/arrlen.png

Chrome 和 Firefox 优化了属性访问器,使其与将长度复制到局部变量一样高效。 IE 和 Opera 不会,而且速度要慢 50% 以上。

然而,请记住,测试结果的“ops/second”是指每秒通过一百万个元素的数组完成的迭代次数

为了正确看待这一点,即使在 IE8(这组中表现最差的)(在属性访问和局部变量上分别得分 0.44 和 3.9)中,每次迭代的损失也只有 2 µs 。使用array.length 迭代一千多个项目只会花费您额外的 2 毫秒。换句话说:提防过早的优化

【讨论】:

  • 为 1,000 个项目节省 2 毫秒并不是过早的优化。此外,如果有 10,000 个项目怎么办?如果代码被频繁调用?
  • 您应该在 * 上发布图片,而不是在您的个人网站上,因为您的个人网站不再可用...
【解决方案2】:

实际数组的长度不是即时计算的。它作为数组数据结构的一部分存储,因此访问它只涉及获取值(没有计算)。因此,它通常与检索对象的任何固定属性一样快。正如你在这个性能测试中看到的,检索数组的长度和检索对象的属性基本上没有区别:

http://jsperf.com/length-comparisons

一个例外是 DOM 从 getElementsByTagName()getElementsByClassName() 等函数返回的 nodeList 对象。在这些情况下,访问长度属性通常要慢得多。这可能是因为这些 nodeList 对象不是真正的 javascript 对象,并且每次从这些对象访问某些内容时都必须跨越 Javascript 和本机代码之间的桥梁。在这种情况下,将长度缓存到局部变量中而不是在 nodeList 的循环中重复使用它会快很多(快 10-100 倍)。我已将其添加到长度比较中,您可以看到它有多慢。

在某些浏览器中,将长度放入局部变量并从那里使用它会明显更快,如果您将一遍又一遍地引用它(例如在循环中)。以下是上述 jsperf 测试的性能图:

【讨论】:

  • 要添加更多关于 length 是属性而不是函数的信息,ECMAScript 2015 语言规范将 Array.length 描述为“一个数据属性,其值始终在数字上大于每个可配置的名称的名称名称为数组索引的属性。” ecma-international.org/ecma-262/6.0/…
【解决方案3】:

所有主要的解释器都为原生数组的长度提供了有效的访问器,但对于像 NodeLists 这样的类数组对象却没有。

"Efficient looping in Javascript"

Test / Browser                Firefox 2.0 Opera 9.1   Internet Explorer 6
Native For-Loop               155 (ms)    121 (ms)    160 (ms)
...
Improved Native While-Loop    120 (ms)    100 (ms)    110 (ms)

"Efficient JavaScript code" 建议

for( var i = 0; i < document.getElementsByTagName('tr').length; i++ ) {
  document.getElementsByTagName('tr')[i].className = 'newclass';
  document.getElementsByTagName('tr')[i].style.color = 'red';
  ...
}

var rows = document.getElementsByTagName('tr');
for( var i = 0; i < rows.length; i++ ) {
  rows[i].className = 'newclass';
  rows[i].style.color = 'red';
  ...
}

这些都不是有效的。 getElementsByTagName 返回一个动态对象,而不是静态数组。每次检查循环条件时,Opera 都必须重新评估对象,并计算出它引用了多少元素,以便计算出length 属性。这比检查静态数字需要更多时间。

【讨论】:

  • 是所有NodeLists 都这样,还是只是活生生的?
  • 当我查看 Mozilla 的数组开发人员网络时,我没有看到为长度提供了太多信息 developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… 但是,为了安全起见,我会预先计算长度并将其存储在一个变量中。我不认为浏览器共享相同的 JS 引擎
  • @santiagobasulto,JavaScript 数组的实现方式不同。这实际上取决于他们realloc 避免复制或拆分块的积极程度,以及他们如何处理大型稀疏数组var arr = []; arr[1e6] = true;。对于小型密集阵列,您可以将它们视为连续的内存块。
  • querySelectorAll 之类的方法返回一个非活动的 NodeList。我会假设它会被预先计算,但是我以前从未想过实时计算实时列表。说得通。我通常会缓存.length,除非我预计循环期间会发生一些变化,所以我想我以前从未真正考虑过。
  • @amnotiam,啊,我在Selectors API Level 1 中看到,“querySelectorAll() 方法返回的 NodeList 对象必须是静态的,而不是实时的([DOM-LEVEL-3-CORE],第 1.1 节.1). 对基础文​​档结构的后续更改不得反映在 NodeList 对象中。这意味着该对象将包含一个匹配的 Element 节点列表,这些节点在创建列表时位于文档中。
【解决方案4】:

由于属性查找速度,通过将长度缓存在局部变量中可能会获得适度的速度提升。这可能会或可能不会被忽略,具体取决于 JS 引擎如何 JIT 代码。

请参阅http://jsperf.com/for-loop-caching 了解基本的 JSperf 测试用例。

【讨论】:

  • 谢谢@AKX,我不认为属性查找有问题。使用 array.length 保持可读性。
  • 这是一个不那么初级的 jsperf 测试用例 jsperf.com/caching-array-length/4
  • 很好的测试!不过,我并没有对其进行迭代。我只是用它的模数来循环它。
【解决方案5】:

对于您不会对其长度进行操作的任何集合类型对象(例如,任何不可变集合),最好缓存其长度以获得更好的性能。

var elems = document.getElementsByName("tst");
var elemsLen = elems.length;
var i;
for(i = 0; i < elemsLen; ++i)
{
  // work with elems... example:
  // elems[i].selected = false;
}
elems = [10,20,30,40,50,60,70,80,90,100];
elemsLen = elems.length;
for(i = 0; i < elemsLen; ++i)
{
  // work with elems... example:
  // elems[i] = elems[i] / 10;
}

【讨论】:

  • 为什么?任何代码?如果没有性能损失,我认为最好使用 .length 方法。我的意思是,如果不是每次我调用它时都计算它。
  • @santiagobasulto 你说得对,'length' 属性并不总是计算出来的,因此在某些浏览器上不会降低性能。但是,在其他浏览器上,缓存到变量比在循环中访问“长度”属性更快。有关更多证据,请参阅jsperf.com/for-loop-caching。然后向下滚动并注意 IE 和 Opera 在缓存 'length' 值而不是通过循环内的属性访问它时表现更好。
最近更新 更多