在JavaScript中对空数组使用for each循环

39

我发现在javascript中不能对空数组使用for each循环。有人能向我解释一下为什么吗?

我已经这样在javascript中初始化了一个数组:

var arr = new Array(10);

当我对数组使用 for each 循环时,什么都不会发生:

arr.forEach(function(i) {
    i = 0;
});

结果仍然是一组未定义值的数组:

arr = [ , , , , , , , , , ];

我认为可能是因为数组中的每个元素都是未定义的,所以它甚至没有执行forEach。我认为它仍然会遍历未定义的元素。有人能解释一下为什么会发生这种情况吗?这个问题并不是在询问如何最有效地用零填充数组,而是在询问forEach循环和空数组的交互细节。


有一个流行的 dup 在某个地方... Array.apply(0, Array(10)).map(function(){return 0}) - elclanrs
2
@adeneo:这将是一个包含 10 个 undefined 的数组。 - gen_Eric
@adeneo:我不知道为什么他只显示成逗号。也许是某些浏览器的显示方式。 - gen_Eric
@RocketHazmat - 这就是我想的,我没有费心去测试,但我很确定我的浏览器控制台只会返回[],正如下面所指出的,forEach永远不会遍历这个数组。 - adeneo
1
@RocketHazmat 警告数组会给出逗号列表(至少在FF中,尽管我认为其他浏览器也是如此)。有趣的是,console.log 给出了 Array [ <10 empty slots> ] - James Montagne
显示剩余2条评论
6个回答

29

如果您将数组初始化修改为以下内容,则可以像您打算的那样使用forEach:

var arr = Array.apply(null, Array(10))

然后你可以使用foreach,如下:

arr.forEach(function(el, index) {
    arr[index] = 0;
});

结果是:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

这会影响性能吗? - Daniel Kobe
1
算法性能不受影响。然而,当谈论性能时,最明智的选择是进行微基准测试。 - Faris Zacina

26

你的想法有一半是对的!

我认为可能是因为数组中每个项目都是未定义的,所以它甚至没有执行forEach。

Array.prototype.forEach不会访问已删除或省略的索引;这个过程称为省略。所以它执行了,但跳过了每个元素。

来自MDN: Screenshot from MDN regarding forEach


1
这并不完全正确。如果像我下面的答案中使用var arr = Array.apply(null, Array(10)),它将通过forEach正确循环未定义的值,就像OP所希望的那样。 - Faris Zacina
2
为了阐述@TheZenCoder的评论,也许你应该修改你的文本,改为说“Array.prototype.forEach不会访问已删除或省略的索引”,正如MDN中所述。它确实会访问那些undefined的元素。 - Simon MᶜKenzie

7

.forEach会对数组中的每个元素运行您的函数。设置i的值没有任何作用,它不是一个引用。

只需使用普通的for循环即可:

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

或者,您可以直接执行以下操作而不是执行new Array(10)

var arr = [];
for(var i = 0; i < 10; i++){
    arr[i] = 0;
}

2
我喜欢这个答案胜过Qambar的。KISS。 - Mallen
1
确实,设置i的值不会有任何作用,因为它不是一个引用。然而,在这种情况下,由于Adam答案中描述的省略,它甚至无法达到那个程度。这里有一个例子 - Simon MᶜKenzie
@SimonMᶜKenzie:谢谢。我没有意识到函数不会运行,因为该值是“未定义的”。 - gen_Eric
@RocketHazmat 这不是真的,你可以使用forEach遍历一个由未定义值填充的数组。 - nikksan

5

根据Faris Zacina的回答,这是一种一行代码的方法。

var zeros = Array.apply(null, Array(10)).map(function(){return 0})

这是用来做什么的? - MartianMartian
1
这将创建一个包含十个零的数组。首先,Array(10) 创建了一个有十个“空”位置的数组,由于空位置不可迭代,我们使用 Array.apply 来绑定一个 this 对象,我们将其设置为 null,并提供参数作为一个数组。所有这些都创建了一个 undefined 的数组,现在它是可迭代的,我们可以用 map 填充它。 - Mark E
2
哦,你想赢得一些积分吗? - MartianMartian

2

控制省略的内容

如上所述,键的存在确定了forEach是否触发,而长度仅决定要检查的键的范围。

另一个有趣的角度是,您可以使用具有has陷阱的Proxy来控制forEach和其他迭代方法可见的内容。

在此示例中,我们过滤奇数索引。

let arr = ['zero', 'one', 'two', 'four']
arr.length = 6

let arrProxy = new Proxy(arr, { has(arr, k){ return k%2===0 } })

arrProxy.forEach( val => { console.log(val) })
//zero
//two
//undefined

这样的策略也让你可以探索其他行为,如果你使用k%2===0代替,则会:
  • true 使你遍历所有空白空间。
  • k != undefined 只允许你遍历已定义的值。
  • k in arr 把你带回起点。
当然,在大多数情况下,你应该使用过滤器,这只是一个演示。

1

如果我对此有所错误,希望有人能纠正我。

在V8源代码array.js:1042中,我发现了这个:

for (var i = 0; i < length; i++) {
      if (i in array) {
        var element = array[i];
        f(element, i, array);
      }
      //...

重要的部分是使用“in”运算符进行条件检查,以确定是否执行传递到forEach中的函数参数。in运算符检查对象内部属性的存在情况。
现在,JS数组只是一个具有编号属性的复杂对象。例如:
var array = [undefined,undefined,undefined];
Object.keys(array); // ["0", "1", "2"]

另一种思考上述数组的方式是将其视为以下对象:
{
  "0": undefined,
  "1": undefined,
  "2": undefined
}

然而,如果您使用另一种构建数组的形式,您会得到以下结果:
var array2 = new Array(3);
Object.keys(array2); // []

这就是为什么您会得到以下结果的原因:
var array = [undefined,undefined,undefined];
var array2 = new Array(3);

array.forEach(function(){console.log('foo')}); //will print foo three times
array2.forEach(function(){console.log('bar')}); //will print nothing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接