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

use*_*194 14 javascript arrays indices

我有一个关于JavaScript数组的通用问题.JavaScript中的数组索引是否作为字符串内部处理?我读到某处,因为数组是JavaScript中的对象,索引实际上是一个字符串.我对此感到有点困惑,并且对任何解释都很高兴.

Poi*_*nty 14

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

如果检查规范的相关部分中的步骤6 ,您将看到属性访问器表达式始终在查找属性之前强制转换为字符串.无论对象是数组实例还是其他类型的对象,都遵循(正式)该过程.(再次,它只是看起来像是发生了什么.)

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

编辑 - 我曾经想过Number.toString要演示数字到字符串的转换,但事实证明规范明确地描述了通过内部进程发生的特定类型转换,而不是通过隐式转换后跟打电话给.toString()(由于性能原因,这可能是一件好事).

  • @user3033194 right - 通过 `[ ]` 运算符用作属性引用的数值将转换为字符串,或者至少规范表明必须执行转换步骤。您给了我一个想法,所以我将扩展答案。 (2认同)
  • @GitaarLAB:现在.在`arr [4294967294] = 42;`之后,``arr.length`正确地显示了`4294967295`.但是,调用`arr.push(21);`抛出一个`RangeError:无效的数组长度`.`arr [arr.length] = 21`有效,但不会改变`length`. (2认同)

Pat*_*son 5

没错,所以:

> 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' ]
Run Code Online (Sandbox Code Playgroud)

  • 对于(console中的var i),log.type(i)显示所有索引的“字符串”。 (4认同)
  • 是的,但是 `[ 'a', 'b', 'c' ].map((_, i) => typeof i)` 返回 `[ 'number', 'number', 'number' ]`。 (2认同)

Git*_*LAB 5

是的,从技术上讲,数组索引是字符串,但正如 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..
Run Code Online (Sandbox Code Playgroud)

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

最好的答案(我认为)来自规范(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(但没有具体说明))“小整数”对于真正的(预初始化的)小整数数组的速度非常快。

另请参阅例如相关问题。

编辑:根据@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 in是JavaScript 中的(我必须添加的无序)属性迭代器,很明显它返回一个字符串(如果没有,我会非常糟糕)。

来自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
Run Code Online (Sandbox Code Playgroud)

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

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

var a = ['a','b','c'];
a['prop']='d';
alert(a);
Run Code Online (Sandbox Code Playgroud)

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

编辑4:

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

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

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