数组长度和未定义索引

21

我想了解JavaScript数组的工作原理,但我这里有一个复杂的问题。

首先我创建了我的数组:

var arr = [];

并在其中设置一些元素:

arr[5] = "a thing";
arr[2] = undefined;

我认为我应该有一个大小为2的数组,因为我只有在2个特定索引处有两个对象。所以我使用了数组的.length属性进行测试:

document.write(arr.length + "<br>");

有趣的是,这个结果是6。但它必须包含两个项目。它的大小如何是6?这可能与我使用的最新索引有关,在这里arr[5] = "a thing";

然后,我尝试遍历它:

var size = 0;
for(var x in arr){
 size++;   
}
size变量现在是2。所以,我从中学到的是:如果我使用一个for in循环,我将计算它有多少个属性,而不是它的最后一个索引。

但是,如果我尝试document.write(arr[4])(它还没有被设置),它会写undefined

那么为什么arr[2]for..in循环中计数,而arr[4]不计数呢?

让我回答我的问题:我正在考虑typeof undefined == undefined这个惊人的真相。但这是JavaScript,我们需要按照它自己的规则来运作 :)

jsFiddle和下面的片段。

var arr = [];

arr[5] = "a thing";
arr[2] = undefined;
document.write(arr.length + "<br>");

var size = 0;
for(var x in arr){
 size++;   
}

document.write(size + "<br>");

document.write(arr[4] + "<br>");


“length” 属性始终至少比最大索引大一个。它是自动调整的。 - RobG
4
重要提示:*不要使用 document.write*,它的作用并非你想象中的那样。例如,它不能“打印一些数据”。这是一个极低级的函数,而现代 JS 有很多更好的方法来做我们在1998年需要 document.write 做的事情。 - Mike 'Pomax' Kamermans
@Mike'Pomax'Kamermans 请参见https://dev59.com/l3RA5IYBdhLWcg3w2xwI#802894/ - guest271314
不好意思,这些问题完全不同。请在评论之前尝试阅读。我在这里的主要问题是为什么未定义在for in循环中不被计算,但我设置的那些被计算。而您的问题完全不同。 - Ahmet Can Güven
@Hacketo 抱歉,我的错误,我本来也要写 true 的。 - Ahmet Can Güven
显示剩余7条评论
7个回答

15

注意:数组索引就是数组对象的属性。

引用MDN的length 与 数字属性之间的关系部分,

 

当在 JavaScript 数组中设置一个属性时,如果该属性是有效的数组索引并且索引超出了当前数组的范围,则引擎将相应地更新数组的 length 属性。

引用ECMA Script 5规范的Array Objects

 

每当添加一个名为数组索引的属性时,如果需要,长度属性会增加一; 并且每当更改长度属性时,其值不小于新长度的每个名为数组索引的属性都会自动删除

因此,当您在索引 5 处设置一个值时,JavaScript 引擎会将 Array 的长度调整为 6


引用ECMA Script 5规范的Array Objects

 

当且仅当 ToString(ToUint32(P)) 等于 P 并且 ToUint32(P) 不等于 232−1 时,属性名 P(以字符串值的形式)才是数组索引。

因此,在您的情况下,24 是有效的索引,但只有 2 在数组中定义。您可以通过以下方式确认:

arr.hasOwnProperty(2)

其他的索引在数组中尚未定义。因此,你的数组对象被称为稀疏数组对象

那么为什么for..in循环中计算arr[2]而不计算arr[4]呢?

for..in枚举对象的所有有效可枚举属性。在您的情况下,由于只有2是数组中的有效属性,它将被计数。

但是,当您打印arr[4]时,它会打印undefined,因为JavaScript会返回undefined,如果您尝试访问对象中未定义的属性。例如,

console.log({}['name']);
// undefined

同样地,由于4arr中没有定义,因此返回undefined


顺便说一下,您可能还想阅读以下答案:


9

一个属性的值为undefined和一个不存在的属性是有区别的,可以使用in操作符来说明:

var obj = {
    one: undefined
};

console.log(obj.one === undefined); // true
console.log(obj.two === undefined); // true

console.log('one' in obj); // true
console.log('two' in obj); // false

当您试图获取不存在的属性值时,仍会获取到undefined值,但这并不意味着它存在。
最后,解释一下您看到的行为:for-in循环只会循环遍历对象中“in”该对象且可枚举的键。
同时,“length”属性只是在当前索引大于或等于当前长度时被调整为比分配的索引多一个。

长度,同时,总是比最高索引属性多一个。 - christopher
1
@Christopher - "...总是至少多一个..."。 - RobG

5

尝试使用.filter()从数组中删除 undefined 值。

var arr = [];
arr[5] = "a thing";
arr[2] = undefined;
arr = arr.filter(Boolean);
document.write(arr.length);


3
这所有的一切都与机器如何处理空间的思想有关。让我们从最简单的想法开始:
var arr =[];

这反过来创建了一个位置,您现在可以存储信息。正如@Mike 'Pomax' Kamermans指出的那样:这个位置是一个特殊的javascript对象,它反过来作为键和值的集合,就像这样:

arr[key] = value;

现在让我们继续看你的代码:
arr[5] = "a thing";

机器现在理解你正在创建位于第6个位置/第5个键上的内容(因为数组的第一个位置是0)。 因此,你最终得到了类似于以下的东西:

arr[,,,,,"a thing"];

那些逗号代表数组中的空位置(正如@RobG所指出的省略)。

当您声明以下内容时,同样的情况也会发生:

arr[2] = undefined;
arr[,,undefined,,,"a thing"];

所以当你使用“for var in”在数组内进行迭代时,你会检查这个数组中每一个已填充的空间,因此是2。
相比之下,当你检查数组的长度时,你要看看数组中有多少个用于存储信息的空间存在,这一点是6。
最后,JavaScript将数组中的空值解释为未定义的值,这就是为什么arr [4]被输出为这样的原因。
希望这回答了你的问题。

1
这对于JS来说并不是真的。JS中的“数组”只是一个JS对象(您知道的那个{}),但具有一些特殊的迭代函数和特殊的键值处理。设置arr = [],然后arr[5] = "something"并不会在“第6个位置”创建一个元素,它只是将属性“something”绑定到对象键5上。数组的length将报告6,但数组本身只包含1个元素。 - Mike 'Pomax' Kamermans
在数组字面量中的“empty positions”是elisions,并且由逗号表示:[,,,,,"a thing"]。这样的成员被称为elided,它们不存在,并且在读取时返回undefined - RobG
@Mike'Pomax'Kamermans并不反对你说的话,因为在JavaScript语言中语义上是正确的。你说得对,数组有键和值,而不是位置。我会编辑我的回答。还有,RobG,我试图以最简单的方式解释这个概念,也发现下划线更容易使用作为表示。无论如何,我不能反对你的语义。 - Orlando Paredes Hamsho

2

JavaScript数组,至少在第一个版本中,是带有length属性的普通对象。你经历的大部分奇怪行为都是由此引起的。

结果很有趣,是6。但它必须包含两个数据,它的大小如何能是6?这可能与我在这里使用的最新索引arr [5] =“a thing”有关。

它的结果是6,因为length总是比最高索引高1,即使实际上数组中的项较少。

那么为什么在for..in循环中计算arr [2]而不计算arr [4]?

因为当你执行:

arr[2] = undefined;

你实际上是向数组对象添加了一个名为2的键。结果,值arr[2]for in循环中被计算,而a[4]则被忽略。


0
Javascript的length属性在你直接给某个索引设置值时不会给出真实的长度。如果你想要真实的长度,那么这是正确的方法。

Object.keys({...arr}).length


0
该赋值语句设置了数组的属性,因此当您使用for var i in风格的循环时,您只会看到已经设置过的属性(即使您将它们设置为未定义)。当您分配一个新的整数属性,例如arr[6],数组会修改数组的长度为7。底层的内存可能会被重新分配,也可能不会,但当您使用它时,它将为您提供支持 - 除非您的系统内存不足。
根据RobG的评论进行了编辑,以符合ECMA-262的规定。

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