我有一个关于 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' ]
是的,从技术上讲,数组索引是字符串,但正如 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 被称为稀疏对象:
令 len 为使用参数“length”调用 O 的 [[Get]] 内部方法的结果。
对于 0≤i<ToUint32(len) 范围内的每个整数 i
一个。令 elem 成为使用参数 ToString(i) 调用 O 的 [[GetOwnProperty]] 内部方法的结果。湾 如果 elem 未定义,则返回 true。
返回假。
有效地,ECMAScript 262 规范只是向 JavaScript 程序员确保了明确的数组引用,而不管获取/设置arr['42']
或arr[42]
高达 32 位无符号。
主要的区别是,例如(自动更新)array.length
,array.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 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
现在使用我的回答中的解释:发生的事情是'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] 之间)创建数组indexes
或elements
;不是properties
。
这是更好地说:两个实际整数和一个string
表示整数(在两者之间[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 中一切都是对象)。但是您没有向标准数组添加元素。