首页 > 解决方案 > 为什么数组允许字符串作为 JavaScript 中的索引?

问题描述

我们已经知道,数组和对象之间的区别之一是:

“如果你想提供特定的键,唯一的选择是一个对象。如果你不关心键,它就是一个数组”(在这里阅读更多)

此外,根据MDN 的文档

数组不能使用字符串作为元素索引(如在关联数组中),但必须使用整数

然而,令我惊讶的是:

> var array = ["hello"]; // Key is numeric index
> array["hi"] = "weird"; // Key is string
content structure looks like:  ["hello", hi: "weird"]

数组的内容结构看起来很奇怪。更重要的是,当我检查它返回的数组类型时true

Array.isArray(array) // true

问题:

  1. 为什么会有这种行为?这似乎不一致,对吧?
  2. 幕后实际存储的数据结构是什么:作为数组或对象、哈希表、链表之类的东西?
  3. 此行为是否取决于特定的 JavaScript 引擎(V8spidermonkey等)?
  4. 我应该在普通对象上使用这样的数组(键是数字索引和字符串)吗?

标签: javascriptarraysobjectspecifications

解决方案


JavaScript 中的数组不是单独的类型,而是 object 的子类(Array实际上是 class 的实例)。数组(和列表)语义分层在 JavaScript 中每个对象自动获得的行为之上。Javascript 对象实际上是关联数组抽象数据类型的实例,也称为字典、映射、表格等,因此可以具有任意命名的属性。

您引用的 MDN 文档的其余部分很重要:

使用方括号表示法(或点表示法)通过非整数设置或访问不会从数组列表本身设置或检索元素,但会设置或访问与该数组的对象属性集合关联的变量。数组的对象属性和数组元素列表是分开的,数组的遍历和变异操作不能应用于这些命名属性。

可以肯定的是,您始终可以在数组上设置任意属性,因为它是一个对象。当你这样做时,根据实现,当你在控制台中显示数组时,你可能会得到一个看起来很奇怪的结果。for但是,如果您使用标准机制( ...of循环或方法)对数组进行迭代,.forEach则不包括这些属性:

> let array = ["hello"] 
> array["hi"] = "weird"
> for (const item of array) { console.log(item) }
hello
> array.forEach( (item) => console.log(item) )
hello

MDN 说“数组的对象属性和数组元素列表是分开的”的说法有些夸张;数组的索引元素只是普通的对象属性,其键恰好是数字。因此,如果您使用..遍历所有属性,那么数字属性将与其他属性一起显示。但是正如有据可查的那样,..忽略了数组的特性,如果您正在寻找类似数组的行为,则不应使用它。这是MDN 的另一个页面,讨论了这一点:forinforinArray

数组迭代和 for...in

注意: for...in 不应用于迭代索引顺序很重要的数组。数组索引只是具有整数名称的可枚举属性,在其他方面与一般对象属性相同。不能保证 for...in 将按任何特定顺序返回索引。for...in 循环语句将返回所有可枚举属性,包括具有非整数名称的属性和继承的属性。

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

所以回答你的问题:

  1. 为什么会有这种行为?这似乎不一致,对吧?

“为什么”的问题总是很难回答,但从根本上说,这种情况下的答案似乎是你所看到的并不是一种有意的行为。据我们所知,Brandon Eich 和随后的 Javascript 设计者并没有着手制作一个也允许非数字键的数组类型。相反,他们选择根本不创建数组类型。在 Javascript 中,只有五种类型:booleannumberstringundefinedobject(包括null)。Array 没有成功——它不是一个单独的类型,而只是一个对象类。事实上,您可以从核心语言中删除数组并完全用 Javascript 本身实现它们(尽管您会失去[...]文字语法的便利性)。

所以这就是为什么 - Javascript 数组不是它们自己的类型,而只是一个对象类;Javascript 中的所有对象都是关联数组;因此,您可以将数组用作关联数组。

  1. 幕后实际存储的数据结构是什么:作为数组或对象、哈希表、链表之类的东西?

ECMAScript 规范要求对象充当关联数组,但没有规定实现;您可以假设它们是哈希表而不失任何一般性。但由于 Array 类通常是核心语言实现的一部分,而不是纯 Javascript 运行时代码,因此我不会惊讶地发现它包含了超出通用对象属性处理代码的特殊优化,用于更有效地处理数字索引值. 只要语义相同,就没有关系。

  1. 此行为是否取决于特定的 Javascript 引擎(V8、SpiderMonkey 等)?

不同的引擎可能会更改数组值的显示/序列化方式,尤其是此类字符串表示是否包含非数字属性。但是能够存储任意键/值对的基本行为是语言设计的自然副作用,并且应该在所有符合 ECMAScript 规范的实现中通用。

  1. 我应该在普通对象上使用这样的数组(键既是数字索引又是字符串)?

好吧,数组一个普通的对象。但是为了让阅读您的代码的人获得最大的可读性和最小的惊喜,我不建议使用相同的对象作为常规数组和关联数组。您可以实现一个具有类似数组行为的新​​类,甚至可以从Array原型继承,但通常最好将这两种数据结构类型分开。


推荐阅读