JavaScript 数组索引是字符串还是整数?

IT技术 javascript arrays indices
2021-01-29 17:17:36

我有一个关于 JavaScript 数组的通用问题。JavaScript 中的数组索引是否在内部作为字符串处理?

我在某处读到,因为数组是 JavaScript 中的对象,所以索引实际上是一个字符串。我对此有点困惑,很高兴能得到任何解释。

5个回答

正式地,所有属性名称都是字符串。这意味着类似数组的数字属性名称实际上与任何其他属性名称没有任何不同。

如果您查看规范相关部分中的第 6 步,您将看到在查找属性之前,属性访问器表达式总是被强制转换为字符串。无论对象是数组实例还是其他类型的对象,都会(正式)遵循该过程。(同样,它看起来就像是正在发生的事情。)

现在,在内部,JavaScript 运行时可以自由地以任何方式实现数组功能。

编辑——我有一个想法Number.toString来证明数字到字符串的转换发生了,但事实证明,规范明确地描述了通过内部过程发生的特定类型转换,而不是隐式转换,然后调用.toString()(出于性能原因,这可能是一件好事)。

@GitaarLAB:现在。之后arr[4294967294] = 42;arr.length正确显示4294967295但是,调用arr.push(21);会抛出一个RangeError: Invalid array length. arr[arr.length] = 21有效,但不会改变length
2021-03-19 17:17:36
是的,看到了,那很快
2021-03-31 17:17:36
好奇害死猫:请你提供一些参考?我记得低于 2^32 的正整数是整数,其他的都是字符串 hashlookup(不过只是在谈论数组)。
2021-04-04 17:17:36
@user3033194 正确 - 通过[ ]运算符用作属性引用的数值被转换为字符串,或者至少规范说必须进行转换步骤。你给了我一个想法,所以我会扩展答案。
2021-04-04 17:17:36
@GitaarLAB 是的——如果你仔细想想,JavaScript 中数组的唯一特别之处就是.length属性发生的一些神奇的事情
2021-04-11 17:17:36

这是正确的,所以:

> var a = ['a','b','c']
undefined
> a
[ 'a', 'b', 'c' ]
> a[0]
'a'
> a['0']
'a'
> a['4'] = 'e'
'e'
> a[3] = 'd'
'd'
> a
[ 'a', 'b', 'c', 'd', 'e' ]
for (var i in a) console.log(typeof i) 显示所有索引的“字符串”。
2021-04-03 17:17:36
是的,但[ 'a', 'b', 'c' ].map((_, i) => typeof i)返回[ 'number', 'number', 'number' ]
2021-04-04 17:17:36

是的,从技术上讲,数组索引是字符串,但正如 Flanagan 在他的“权威指南”中优雅地指出的那样:“清楚地区分数组索引和对象属性名称是有帮助的。所有索引都是属性名称,但只有属性名称是0 到 2 32 -1之间的整数是索引。”

通常你不应该关心浏览器(或更一般的“脚本主机”)在内部做什么,只要结果符合可预测的和(通常/希望)指定的结果。事实上,在 JavaScript(或 ECMAScript 262)的情况下,仅根据需要哪些概念性步骤来描述。这(有意地)为脚本宿主(和浏览器)留下了空间,以提出更小、更快的聪明方法来实现指定的行为。

事实上,现代浏览器在内部对不同类型的数组使用了许多不同的算法:重要的是它们包含什么、它们有多大、它们是否有序、它们是否在 (JIT) 编译时固定和优化,或者是否它们是稀疏的或密集的(是的,它通常是值得的,new Array(length_val)而不是 ninja [])。

在您的思维概念中(在学习 JavaScript 时),了解数组只是一种特殊类型的对象可能会有所帮助。但它们并不总是与人们所期望的相同,例如:

var a=[];
a['4294967295']="I'm not the only one..";
a['4294967296']="Yes you are..";
alert(a);  // === I'm not the only one..

尽管对于不知情的程序员来说,拥有一个数组(带有索引)并将属性附加到数组对象是很容易且相当透明的。

最好的答案(我认为)来自规范(15.4)本身:

数组对象

数组对象对特定类别的属性名称进行特殊处理。属性名称 P(以字符串值的形式)是数组索引,当且仅当 ToString(ToUint32(P)) 等于 P 且 ToUint32(P) 不等于 2 32 -1属性名称为数组索引的属性也称为元素。每个 Array 对象都有一个长度属性,其值始终是一个小于 2 32的非负整数. length 属性的值在数字上大于名称为数组索引的每个属性的名称;每当创建或更改 Array 对象的属性时,都会根据需要调整其他属性以保持此不变性。具体来说,每当添加名称为数组索引的属性时,如果需要,将长度属性更改为比该数组索引的数值大 1;每当更改长度属性时,名称为数组索引且值不小于新长度的每个属性都会自动删除。此约束仅适用于 Array 对象的自身属性,不受可能从其原型继承的长度或数组索引属性的影响。

如果以下算法返回 true,则对象 O 被称为稀疏对象:

  1. 令 len 为使用参数“length”调用 O 的 [[Get]] 内部方法的结果。

  2. 对于 0≤i<ToUint32(len) 范围内的每个整数 i

    一个。令 elem 成为使用参数 ToString(i) 调用 O 的 [[GetOwnProperty]] 内部方法的结果。如果 elem 未定义,则返回 true。

  3. 返回假。

有效地,ECMAScript 262 规范只是向 JavaScript 程序员确保了明确的数组引用,而不管获取/设置arr['42']arr[42]高达 32 位无符号。

主要的区别是,例如(自动更新)array.lengtharray.push以及其他阵列糖状array.concat等。虽然,是的,JavaScript的也让超过已设置为一个对象的属性一个循环中,我们看不懂,我们有多少集(没有循环)。 是的,据我所知,现代浏览器(尤其是他们所谓的 chrome(但没有具体说明))“小整数”对真正的(预初始化的)small-int 数组非常快。

另请参阅例如相关问题。

编辑:根据@Felix Kling 的测试(来自他上面的评论):

之后arr[4294967294] = 42;arr.length正确显示4294967295但是,调用arr.push(21); 抛出一个RangeError: Invalid array length. arr[arr.length] = 21有效,但不会改变长度。

在此答案之后,对这种(可预测的和预期的)行为的解释应该很清楚。

编辑2:

现在,有人发表了评论:

for (var i in a) console.log(typeof i) 显示所有索引的“字符串”。

由于for inJavaScript 中的(我必须添加的无序)属性迭代器,很明显它返回一个字符串(如果没有,我会非常糟糕)。

来自MDN

for..in 不应用于迭代索引顺序很重要的数组。

数组索引只是具有整数名称的可枚举属性,在其他方面与一般对象属性相同。无法保证 for...in 会以任何特定顺序返回索引,并且会返回所有可枚举属性,包括具有非整数名称的属性和继承的属性。

因为迭代顺序取决于实现,所以迭代数组可能不会以一致的顺序访问元素。因此,在迭代访问顺序很重要的数组时,最好使用带有数字索引的 for 循环(或 Array.forEach 或 for...of 循环)。

所以我们学了什么?如果顺序对我们很重要(通常是数组),那么我们在 JavaScript 中需要这个古怪的数组,并且具有“长度”对于按数字顺序循环非常有用。

现在想想替代方案:给你的对象一个 id/order,但是你需要再次为每个下一个 id/order(属性)循环你的对象......

编辑3:

有人回答是这样的:

var a = ['a','b','c'];
a['4'] = 'e';
a[3] = 'd';
alert(a); // returns a,b,c,d,e

现在使用我的回答中的解释:发生的事情是'4'可以强制转换为整数4,并且在[0, 4294967295]使其成为有效数组的范围内index也称为element. 由于 vara是一个数组 ( []),数组元素4 被添加为数组元素,而不是属性(如果 vara是一个对象 ( {})会发生什么)。

进一步概述数组和对象之间区别的示例:

var a = ['a','b','c'];
a['prop']='d';
alert(a);

看看它是如何a,b,c在没有“d”的情况下返回的

编辑4:

您评论说:“在这种情况下,整数索引应该作为字符串处理,因为它是数组的一个属性,它是一种特殊类型的 JavaScript 对象。” 在术语方面错误的,因为:(表示字符串)整数索引(在 [0, 4294967295] 之间)创建数组indexeselements不是properties

这是更好地说:两个实际整数一个string表示整数(在两者之间[0,4294967295])是一个有效的数组索引(并且应该在概念上被视为整数),并创建/改变阵列元件(的“事物” /值(仅)在您执行arr.join()arr.concat()例如执行时返回)。

其他一切都会创建/更改属性(并且在概念上应该被视为字符串)。浏览器真正做什么,通常不应该让您感兴趣,请注意,您指定的代码越简单和清晰,浏览器就越有可能认识到:“哦,让我们将其优化为引擎盖下的实际数组”。

不,我不是唯一一个谁这么说的:自Axel Rauschmayer先生博士的博客array indices in JavaScript are actually strings. Naturally, engines perform optimizations under the hood so that, internally, that is not true. But it is how the spec defines themPretend array indices are numbers. That’s what usually happens under the hood and the general direction in which ECMAScript is moving.有效ECMAScript的262规格只是确保用户明确的数组引用不论获取/设置的'9'9高达为32位无符号
2021-03-16 17:17:36

让我们来看看:

[1]["0"] === 1 // true

哦,但这还不是决定性的,因为运行时可能会强制转换"0"+"0"+"0" === 0

[1][false] === undefined // true

现在,+false === 0所以不,运行时不会将值强制转换为数字。

var arr = [];
arr.false = "foobar";
arr[false] === "foobar" // true

所以实际上,运行时将值强制转换为字符串。所以是的,它是一个哈希表查找(外部)。

这对我来说是全新的。我曾经认为 JS 数组索引就像其他语言中的数组索引。
2021-04-08 17:17:36
请记住,运行时可能在内部将数组表示为传统数组以提高性能。但对用户来说,数组只是一个对象。
2021-04-08 17:17:36

在 JavaScript 中有两种类型的数组:标准数组和关联数组(或具有属性的对象)

  • [ ] - 标准数组 - 仅基于 0 的整数索引
  • { } - 关联数组 - JavaScript 对象,其中键可以是任何字符串

所以 ...

var arr = [ 0, 1, 2, 3 ];

... 被定义为一个标准数组,其中索引只能是整数。当您执行 arr["something"] 时,因为 something(您用作索引的内容)不是整数,您基本上是在为 arr 对象定义一个属性(在 JavaScript 中一切都是对象)。但是您没有向标准数组添加元素。

JavaScript 对象在许多方面的行为都类似于“关联数组”,但它们实际上不是一回事,并且规范从未使用该术语。
2021-03-18 17:17:36
我只是调整了该术语的使用。
2021-03-18 17:17:36
将数组描述为一种对象而不是相反的方式可能更准确。
2021-03-21 17:17:36