为什么不建议使用JavaScript的For...In循环来处理数组?

14

我在某个地方读到(抱歉,找不到链接),For...In循环不建议用于数组。 这里说: http://www.openjs.com/articles/for_loop.php 它是用于关联数组的,并且在 http://www.w3schools.com/js/js_loop_for_in.asp 中也说其用于迭代对象的所有属性(没有说可以用于数组)。我不知道该相信谁。 我不想让这个问题变成辩论。我只想知道如果我在代码中使用它是否会有意外的副作用。 谢谢!


3
W3Schools?W3Fools! 尽管说实话,声称JavaScript有“关联数组”的任何内容也是错误的。 - Gareth
@Gareth 不错的链接,不过 JavaScript 确实"关联数组"。Perl甚至称之为这样,尽管在其他地方,“字典”或“映射”是常见术语。 (我更喜欢避免使用“关联数组”这个术语,因为我喜欢保持与“正常数组”ADT的区别;但这并不意味着该术语不正确)。 - user166390
@pst 很多人将 JavaScript 对象视为关联数组,但它们并不完全相同。这有点学究,但区别在于所有对象属性都是字符串。例如,array = {}; array[42] = 'foo'; array["42"] // => 'foo' (!)。对于数字来说,这并不太疯狂,但对于对象(我希望能够将其用作“关联数组”中的键),它就行不通了:var key1 = {name: 'Gareth'}, key2 = {}, array = {}; array[key1] = 'awesome'; array[key2] // => 'awesome' (!) 在这种情况下,“键”都具有相同的 .toString(),因此设置了相同的属性。 - Gareth
@pst 注意,你所链接的维基百科页面的第一部分要求之一是“关联数组的键也可以是任意类型”。 - Gareth
4个回答

21

数组是一个对象,数组元素只是将数字索引转换为字符串的属性。例如,arr[123]指的是数组对象arr中的一个名为“123”的属性。

for ... in结构适用于所有对象,不仅仅是数组,这就导致了混淆。

当有人对数组使用for ... in时,大多数情况下程序员想要迭代所有的元素,甚至很可能按顺序迭代。例如,如果数组保存了一些数字,则程序员最有可能想要迭代一系列数字。这个语义与其他编程语言中数组迭代非常相似,因此很容易让人混淆。

在JavaScript中,这个结构不会按顺序迭代数组元素。它会迭代所有数组的属性名称(包括继承的原型函数的名称、添加到它的任何属性、添加到它的任何其他非元素属性等),而且没有顺序可言。在早期的浏览器中,它甚至可以找到属性length,尽管在最近的浏览器中,为了避免这种情况发生,这些属性现在被定义为隐藏属性。

对于上面的整数数组,你得到的不是一系列数字,而是一系列文本字符串。不是元素值,而是属性名称(这些名称只是没有顺序的数字索引)。如果数组中存储的元素恰好是相似的数字值,它会让所有人都感到困惑。

因此,你不应该这么做。不应该使用一个看起来像做明显事情的语言结构,但实际上做的事情完全不同。这会创建非常难以找到的非常难以理解的错误。


3
+1,特别是“为了”这部分。 - Cameron
1
说实话,这个回答比标记为重复的原始回答更清楚地解释了原因,哈哈。 - ey dee ey em

2
我已在多个浏览器(FireFox 3、Opera 9、IE6、IE9 beta、Chrome)中测试了数组迭代,它可以正常工作;我记得有些跨浏览器的不兼容问题,但我可能是错了。
然而还有一个警告:
如你所提到的,使用for ... in语法来迭代对象的属性;因此,对于数组来说,除了元素之外,也会迭代所有该数组对象的属性。通常,数组对象只有与其键相对应的属性,但如果另一个脚本修改了Array.prototype(例如某些框架),那么添加的方法/属性也会意外地显示在迭代中。

你能给出这些浏览器的例子吗? - John Bautista
@Jairo:我记不清具体是哪些浏览器了,但我清楚地记得我不得不修复我的所有代码,因为我希望它能够跨浏览器兼容,但在我测试的某个浏览器上无法正常工作(可能是IE6?) - Cameron
IE6已经过时了:P。嗯,感谢你澄清你的答案。 - John Bautista
1
@Jairo:我刚刚在一堆浏览器(包括IE6)上测试了一个使用for ... in的示例,它运行良好,我必须因为其他原因改变了我的JS(可能是原型的问题)。 - Cameron

2
在Prototype.js库文档中提供了几个很好的理由:http://www.prototypejs.org/api/array 基本上,使用for...in来迭代数组是脆弱的,因为任何其他代码都可以向Array原型中添加属性,这些属性将成为每个数组对象的可枚举属性。

0

使用for(... in ...)迭代数组将无法获取数字键,而是获取字符串值。

它将获取在原型上定义的属性,因此如果任何代码扩展了Array,例如通过执行以下操作:

Array.prototype.each = ...;

那么您将看到属性each

不能保证以数组索引顺序获取属性。例如,尝试迭代:

var arr = []
arr[1] = 1;
arr[0] = 0;

在许多浏览器上,您将在0之前得到1
并且不能保证获取所有索引。尝试迭代。
[0,,,3]

你将无法获取索引为 12 的值。

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