我有一个关于JavaScript数组的普遍问题。在JavaScript中,数组索引是作为字符串内部处理的吗?
我曾经在某处读到,因为JavaScript中的数组是对象,所以索引实际上是一个字符串。我对此有点困惑,希望得到任何解释。
我有一个关于JavaScript数组的普遍问题。在JavaScript中,数组索引是作为字符串内部处理的吗?
我曾经在某处读到,因为JavaScript中的数组是对象,所以索引实际上是一个字符串。我对此有点困惑,希望得到任何解释。
严格地说,所有的属性名都是字符串。这意味着类似于数组的数字属性名与任何其他属性名并没有什么不同。
如果您检查规范中相关部分的第6步,您将发现属性访问器表达式在查找属性之前总是被强制转换为字符串。无论对象是数组实例还是其他类型的对象,这个过程(形式上)都会被遵循。(再次强调,这只是看起来是这样。)
现在,JavaScript运行时在内部实现数组功能的方式是自由的。
编辑 — 我有一个想法,可以玩一下Number.toString
以证明发生数字到字符串的转换,但事实证明规范明确描述了该特定类型的转换是通过内部过程进行的,并非通过隐式强制类型转换后调用.toString()
(出于性能原因,这可能是一个好事情)。
那么正确的做法如下:
> 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)
显示所有索引的类型都是 'string'。 - RobG[ 'a', 'b', 'c' ].map((_, i) => typeof i)
返回 [ 'number', 'number', 'number' ]
。 - dbkaplun是的,从技术上讲,数组索引是字符串,但正如Flanagan在他的“Definitive guide”中所说:
清楚地区分数组索引和对象属性名是有帮助的。所有索引都是属性名,但只有介于0和232-1之间的整数属性名才是索引。
通常情况下,您不应关心浏览器(或更一般的“脚本主机”)在内部执行什么操作,只要结果符合可预测且(通常/希望)指定的结果即可。实际上,在JavaScript(或ECMAScript 262)的情况下,只描述了需要哪些概念步骤。这(故意)为脚本主机(和浏览器)提供了创造聪明、更小、更快速的方法来实现指定行为的空间。
事实上,现代浏览器在内部使用多种不同的算法来处理不同类型的数组:它们包含什么、大小如何、是否有序、是否可以在(JIT)编译时进行优化,以及它们是否稀疏或密集(是的,经常使用new Array(length_val)
而不是[]
)。
var a=[];
a['4294967295']="I'm not the only one..";
a['4294967296']="Yes you are..";
alert(a); // === I'm not the only one..
有效地,ECMAScript 262规范确保了JavaScript程序员无论是获取/设置arr['42']还是arr[42],都能获得明确的数组引用,最高可达32位无符号整数。数组对象
数组对象对某些属性名给予特殊处理。当一个属性名P(以字符串形式表示)满足ToString(ToUint32(P))等于P且ToUint32(P)不等于2的32次方减1时,它就是一个数组索引。其属性也称为元素。每个数组对象都有一个长度属性,其值始终为小于2的32次方的非负整数。长度属性的值大于每个属性名为数组索引的属性的名称;无论何时创建或更改数组对象的属性,其他属性都会根据需要进行调整以保持此不变量。具体而言,每当添加一个名称为数组索引的属性时,如果必要,就会更改长度属性,使其比该数组索引的数值多一;每当更改长度属性时,每个名称为数组索引且其值不小于新长度的属性都会自动删除。这个限制仅适用于数组对象的自有属性,并且不受从其原型继承的长度或数组索引属性的影响。
如果以下算法返回true,则称对象O为稀疏对象:
使用参数“length”调用O的[[Get]]内部方法,将结果赋给len。
对于范围在0≤i<ToUint32(len)中的每个整数i
a. 使用参数ToString(i)调用O的[[GetOwnProperty]]内部方法,将结果赋给elem。
b. 如果elem未定义,则返回true。
返回false。
arr[4294967294] = 42;
之后,arr.length
正确显示为4294967295
。但是调用arr.push(21)
会抛出一个RangeError: Invalid array length
异常。使用arr[arr.length] = 21
可以正常工作,但不会改变数组长度。由于for (var i in a) console.log(typeof i) shows 'string' for all indexes.
for in
是JavaScript中的(无序必须补充)属性迭代器,很明显它返回一个字符串(如果它没有这样做,我会非常困惑)。for..in循环不应用于需要保持索引顺序的数组。
数组索引只是具有整数名称的可枚举属性,与普通对象属性完全相同。没有保证for...in将以任何特定顺序返回索引,并且它将返回所有可枚举属性,包括那些具有非整数名称和那些被继承的属性。
由于迭代顺序取决于实现方式,因此迭代数组可能无法按一致的顺序访问元素。因此,在迭代访问顺序重要的数组时,最好使用带有数字索引的for循环(或Array.forEach或for...of循环)。
那么我们学到了什么?如果顺序对我们很重要(通常在数组中是如此),那么我们就需要JavaScript中这种奇怪的数组,并且拥有“长度”对于按数字顺序循环而言相当有用。
现在想想替代方案:给你的对象一个id/顺序,但然后你又需要为每个下一个id/顺序(属性)再次循环遍历你的对象...
编辑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
。由于变量 a
是一个数组([]
),所以数组元素 4 被添加为数组元素,而不是属性(如果变量 a
是一个对象({}
),那么就会发生这种情况)。var a = ['a','b','c'];
a['prop']='d';
alert(a);
看看它如何返回a,b,c
,没有看到'd'。
编辑4:
你评论说:"在这种情况下,整数索引应该被处理为字符串,因为它是数组的属性,而数组是JavaScript对象的一种特殊类型。"
从术语上讲,这是错误的,因为:(表示)整数索引(介于[0,4294967295]之间)创建数组索引
或元素
;而不是属性
。
最好这样说:实际整数和表示整数的字符串
(都介于[0,4294967295]之间)都是有效的数组索引(并且在概念上应该被视为整数),并且创建/更改数组元素(只有当您执行arr.join()
或arr.concat()
时才会返回“东西”/值)。
除此之外,所有的操作都会创建/改变一个属性(并且在概念上应该被视为字符串)。 浏览器实际上所做的事情通常不应该引起您的兴趣,需要注意的是,编写更简单、更清晰的代码可以让浏览器更好地识别:“哦,让我们在底层优化为一个真正的数组”。
让我们来看看:
[1]["0"] === 1 // true
哦,但这并不是确定的,因为运行时可能会将"0"
强制转换为+"0"
和+"0" === 0
。
[1][false] === undefined // true
+false === 0
,因此运行时不会将该值强制转换为数字。var arr = [];
arr.false = "foobar";
arr[false] === "foobar" // true
实际上,运行时将该值强制转换为字符串。是的,这是一个哈希表查找(外部)。
在JavaScript中,有两种类型的数组:标准数组和关联数组(或具有属性的对象)
因此...
var arr = [ 0, 1, 2, 3 ];
标准数组是指只能使用整数作为索引的数组。当您使用 arr["something"] 时,由于 something(即用作索引的内容)不是整数,因此您实际上是在将属性定义给 arr 对象(在 JavaScript 中,所有内容都是对象)。但您没有向标准数组添加元素。
[ ]
运算符用作属性引用的数字值会被转换为字符串,或者至少规范要求进行转换步骤。你给了我一个想法,所以我会扩展答案。 - Pointy.length
属性发生的有些神奇的事情。 - Pointyarr[4294967294] = 42;
后,arr.length
正确显示为4294967295
。但是,调用arr.push(21);
会抛出一个RangeError: Invalid array length
错误。arr[arr.length] = 21
可以工作,但不会改变length
。 - Felix Kling