JavaScript对象数组长度不正确

61

能否有人解释一下这个(奇怪的)行为?为什么第一个例子的长度是3而不是2,更重要的是,为什么第二个例子的长度是0?只要键是数字,长度就有效。当它们不是数字时,长度为0。我该如何从第二个示例中获取正确的长度?谢谢。

a = [];
a["1"] = {"string1":"string","string2":"string"};
a["2"] = {"string1":"string","string2":"string"};
alert(a.length); // returns 3

b = [];
b["key1"] = {"string1":"string","string2":"string"};
b["key2"] = {"string1":"string","string2":"string"};
alert(b.length); // returns 0
3个回答

97

需要注意的一点是常规数组和关联数组之间有区别。在常规数组(真正的数组)中,索引必须是整数。另一方面,关联数组可以使用字符串作为索引。如果您愿意,可以将关联数组视为映射。现在,请注意,真正的数组始终从零开始。因此,在您的示例中,您是按以下方式创建了一个数组:

a = [];
a["1"] = {"string1":"string","string2":"string"};
a["2"] = {"string1":"string","string2":"string"}

Javascript可以将字符串索引转换为数字,因此,您上面的代码变为:

a = [];
a[1] = {"blah"};
a[2] = {"blah"};

但请记住我之前说过的:真正的数组从零开始。 因此,Javascript解释器自动将a [0]分配给未定义的值。 在firebug或chrome / safari控制台中尝试它,您会在尝试打印“a”时看到类似于这样的内容。您应该获得类似于“[undefined,Object,Object]”的东西。因此大小为3而不是您期望的2。

在您的第二个示例中,我非常确定您正在尝试模拟使用关联数组,其本质上是向对象添加属性。 请记住,关联数组使您可以使用字符串作为键。 换句话说,您正在向对象添加属性。 所以在您的示例中:

b["key1"] = {"string1":"string","string2":"string"};

这真正意味着:

b.key1 = {"string1":"string","string2":"string"};

初始化b=[]只是创建一个数组,但你的赋值并没有填充数组,它只是给“b”添加了额外的属性。


10
解释一个常见的误解:关联数组实际上并不是数组,它们只是复杂对象。 +1 - plodder
在JS中,数组并不一定是指连续的内存区域,其中索引是从开头偏移的。但这些细节并不重要。 - outis
3
这段代码是计算数组或对象的大小,可以通过调用 Object.size(yourArray) 来获取大小,无论索引从哪里开始或键是否为字符串,都能正确返回大小。 - Tom
虽然JS数组的起始索引是0,但这并不是普遍的情况。有些语言从1开始索引,而其他语言允许程序员声明每个数组的起始索引。 - outis
在许多JS实现中,数组是稀疏的,因此在设置元素时跳过索引不会设置中间的元素。在示例中,a[0] 实际上不会被设置为 undefined;对于未设置的所有索引,都将返回 undefined - outis
在JS中,数组不是“真正的数组”,如本答案所述;在JS中,数组和关联数组/字符串映射之间的区别不太相关,因为所有对象本质上都是关联数组,数组是对象,因此数组是关联数组。 - outis

19

length返回对象中最大整数键值+1。

a中,最大的键是2,所以1+2等于3。

b中没有整数键(那里的键是key1key2,无法转换为整数),因此Javascript假定最大的键是-1,并且1 + -1得到0

这个程序将帮助您看到:

a = [];
a["1"] = {};
a["4"] = {};
alert(a.length); // Prints 5

谢谢你的回答。我猜这是一个“特性” :). 那么,有没有一种方法可以在不手动计数的情况下获取数组b中的项目数量?还是这是唯一的方法:var len = 0; for (var name in b) len++; - Serenti
不,那是唯一的方法。在JavaScript中,“Object”并不是真正的关联数组;你可以大多数情况下像使用关联数组一样使用它,但是有陷阱。 - bobince
对象根本不是数组。混淆的原因在于一种通过a[b]语法分配属性的方式……但这与说a.b没有任何区别。 - plodder
+1 - 非常好的解释。这使得关于长度属性返回1 + 最大值的JavaScript数组被定义为“稀疏”的概念变得清晰明了。 - Travis J

9

来自ECMAScript标准,ECMA-262,第5版。

15.4.5.2 length

该Array对象的length属性是一个数据属性,其值始终比名称为数组索引的每个可删除属性的名称大。

请注意,数组的length属性仅考虑整数类型的数组索引;设置其他属性不会影响长度。

对于数组,a["3"]等同于a[3](此行为由§ 15.4.5.1指定);3是一个数组索引而不是属性。因此,设置a["3"]会影响数组的长度。b["key1"]等同于b.key1。设置属性不会影响集合的长度。


实际上正好相反:所有键都是字符串,所以a[3]等同于a["3"]。如果您说 for (i in array),则 i 将是 "3" 而不是 3。写下 a[1000000000000000000000]= true,您将获得一个带有字符串键 "1e+21" 的数组,而长度不受影响。 - bobince
@bobince:1e21是一个不同的情况。由于它比2 ** 32-1大(太大而无法成为32位int),因此它不是数组索引。等价性是对称的,因此'a [3]等同于a ["3"]'与'a [3]等同于a ["3"]'表达的是相同的意思。我的观点是将数组索引放在引号中对行为没有影响,而不是元素名称是整数,就像其他语言一样。关于自动转换的说法是误导性的;我会重新写一下。 - outis
@bobince:我试图忽略实现、模型(按照标准规定)和概念(程序员头脑中的处理方式)之间的差异,这只会让事情变得更加复杂。 - outis

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