.forEach 与 Object.keys().forEach 在稀疏数组上的表现

.forEach vs Object.keys().forEach performance on sparse arrays

如果我错了请告诉我:array.forEach(callbackFunction) 适用于稀疏数组。它不对零和数组长度之间的每个索引执行 callbackFunction,而是仅对实际上是 in 数组的键执行。而且(如果我错了请告诉我)这些键正是 Object.keys(array) 给我的。因此(告诉我 为什么 我错了)如果在 array 本身或 [=18= 上调用 .forEach 方法应该没有什么不同].那么,到底为什么会存在这种性能差异 - 就好像在一种情况下会执行从零到长度的巨大无意义循环,但在另一种情况下不会。

显示性能差异的代码段:

function doNothing(){}
CONSOLE = document.getElementById('console');

arr = [];
arr[49888999] = 42;

start = performance.now();
arr.forEach(doNothing);
duration1 = performance.now() - start;

start = performance.now();
Object.keys(arr).forEach(doNothing);
duration2 = performance.now() - start;

CONSOLE.textContent = [duration1, duration2].join('\n');
<pre id='console'></pre>

显示回调函数两种情况中仅被调用一次的代码段

console1 = document.getElementById('console1');
console2 = document.getElementById('console2');
function doNothingVerbose1(){
  console1.textContent = 1 + (+console1.textContent);
}
function doNothingVerbose2(){
  console2.textContent = 1 + (+console2.textContent);
}

arr = [];
arr[49888999] = 42;

start = performance.now();
arr.forEach(doNothingVerbose1);
duration1 = performance.now() - start;

start = performance.now();
Object.keys(arr).forEach(doNothingVerbose2);
duration2 = performance.now() - start;

console.log(duration1, duration2);
~~~~~ 1 ~~~~~
<pre id='console1'>0</pre>
~~~~~ 2 ~~~~~
<pre id='console2'>0</pre>

更新

我刚刚做了一个测试,看看上面的 arr=[];arr[49888999]=42; 是否是一个真正的稀疏数组,也就是说,与 arr=new Array(49889000) 相比,内存占用要少得多。是的,就是这样。在循环中执行数百次,稀疏版本需要几秒钟但不会崩溃,但 new Array(50 million) 版本会使 fiddle 崩溃。因此,如果它没有作为 'normal C++ array' 存储在引擎中,那么引擎必须“拥有”数组的 Object.keys,那么为什么引擎不能有效地利用它呢?我可能对 JS 引擎必须做什么有过于简单的看法;说引擎必须“有”Object.keys 是因为它“有”一个 sparse 数组实现以某种方式支持我们的变量 arr 是错误的吗?也许真正在 browser/JS 引擎上工作的人可以在这里阐明一些问题。

above test on jsperf

好吧,好吧,好吧 - 所以这只是人们必须忍受的事情之一;我不想听到那个,但是正确的答案。

我将继续不阅读规格,有时会感到困惑。不,我不推荐这种行为,这只是我的方式。在控制台上尝试对我来说更有意义,它肯定更有趣,而规格往往会让我睡着。还好人是不一样的,不是每个人都是这样的。

也许更有趣的问题是如何在实践中处理这种现象。例如,如果我必须处理 'sparse Array',如“2 项产品 51472 和 1 项产品 81369”,我将使用 object{}) 键为 51472 和 81369,并且 不是 数组 ([]).

仅仅因为所有的键恰好都是非负整数就把它变成一个数组是一个坏主意过去一万年最糟糕的主意 - 因为你有 .forEach,这是一个 FALSE FRIEND

2个相关问题:

Why are we allowed to create sparse arrays in JavaScript

What use cases are there in JavaScript for Sparse Arrays?

as if because, in one case, a giant pointless loop from zero to length would be is executed, but not in the other case.


根据ECMA documentation

  1. .forEach 方法将通过其 .length 属性.
  2. 遍历所有数组元素
  3. 传递给 .forEach 的回调只有在元素不为空时才会被调用。

为了演示这一点,您可以简单地执行以下操作:

function doNothing(){}
let perf;


console.log('Array with 50 million length and 1 non-empty element:');
const a = [];
a[49999999] = 'a';
console.log('a.length:', a.length);

perf = performance.now();
a.forEach(doNothing);
console.log('a:', performance.now() - perf);
console.log('');


console.log('Array with 0 length:');
const b = [];
b.foo = 'a';
console.log('b.length:', b.length);

perf = performance.now();
b.forEach(doNothing);
console.log('b:', performance.now() - perf);
console.log('');


console.log('Array with 50 million length and 0 non-empty element:');
const c = [];
c.length = 50000000;
console.log('c.length:', c.length);

perf = performance.now();
c.forEach(doNothing);
console.log('c:', performance.now() - perf);