JavaScript对象中的键只能是字符串吗?

65

jshashtable 表示:

JavaScript 的内置对象提供了使用方括号表示法作为属性的哈希表功能,只要您的键是字符串或数字:

据我所知,键只能是字符串(因为数字会被强制转换为字符串)。我只是想确认一下上面所说的是否错误(因为键不能是数字)。

ECMA 标准有没有关于此事的规定?

还是实现是特定于浏览器的?


1
我现在已经更新了jshashtable的文档。正如我在其中一个答案的评论中提到的那样,我试图保持简单,但实际上它只是模糊不清且可能是错误的,所以你对此有所指出是正确的。 - Tim Down
2
@Tim Down 我还不是你的粉丝;-) 这里有一个建议的方法:“不。虽然JavaScript对象可以用作哈希,但有几个限制使得使用JavaScript对象不适合用于通用哈希。其中一个限制是只有字符串和数字才能成为有用的键。”(然后继续展示案例并解释更多细节等,但保持介绍和避免承诺“只有字符串”等) - user166390
1
@pst:是的,你的版本肯定是一个改进。我几乎逐字使用了它,非常感谢你的贡献。 - Tim Down
在不久的将来,WeakMap 将会做到这一点:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakMap#Why_WeakMap.3F - GameAlchemist
8个回答

47
JavaScript的内置对象确实使用方括号符号来提供哈希表功能,只要您的键是字符串或数字。但这似乎是不正确的 - 对象键可以是字符串或(自ECMAScript 2015,又称ECMA-262 ed 6)符号。但这是与方括号属性访问不同的主题。请参见ECMA-262 ed 3 § 11.2.1(还请参见ECMAScript 2017(草案)。):
属性通过名称访问,可以使用点表示法或方括号表示法。点表示法的语法转换与方括号表示法相同。在使用点表示法时,点后面的部分必须符合IdentifierName的条件。但是在使用方括号时,提供一个表达式,该表达式将被求值并解析为一个字符串。
简而言之,方括号表示法提供了一种使用表达式访问属性的方法,例如:
var y = {};
var x = 'foo';
y[x] = 'foo value';

在上面的代码中,x 用方括号提供,因此它被计算并返回字符串 'foo'。由于这个属性在 y 上不存在,所以它被添加了进去。然后将 yfoo 属性赋值为 'foo value'。
一般来说,方括号内的表达式会被计算,并调用其 toString() 方法。这个值就是作为属性名使用的。
在点属性访问方法中,标识符不会被计算,因此:
y.bar = 'bar value';

创建一个具有值bar value的属性bar

如果您想创建一个数字属性,则:

y[5] = 5;

将评估 5,发现它不是一个字符串,调用(或多或少地)Number(5).toString()返回字符串5,该字符串用于属性名称。然后将其赋值为数字5

此答案是在ECMAScript第3版时编写的,但是事情已经发展了。请参阅更高版本的参考和MDN


然而,(从引用中提取的)参数并不有效--尽管结论是有效的。CallExpression.IdentCallExpression["Ident"] 相同并不意味着 CallExpression[42]CallExpression["42"] 相同(尽管它们是相同的--而且在规范中有确切的措辞)。 - user166390
1
@pst:这句话是我几年前说的。当时我知道它并不完全准确,但是为了简化问题而避免引用规范。然而,现在它已经引起了混乱,而且现在看来也不够准确,所以我会修改它。 - Tim Down
@pst:jshashtable文档已更新。我仍然希望避免过于详细和繁琐的规范,但我已经消除了字符串/数字属性名称的问题,并在对象和基元的一般方向上做出了一些努力。 - Tim Down
6
好的,为确保准确无误,每当我们执行 array[1] 时,它实际上会转换为 array["1"] 吗? - Pacerier
2
@Pacerier:不错的总结,这正是我需要快速了解的。 - Sam Vloeberghs

13

你是对的。键只能是字符串,数值键如数组中使用的键会被强制转换为字符串并存储。

var arr = [true];
arr[0] === true;
arr['0'] = false;
arr[0] === false;

ECMAScript规范,第42页:ECMA-262 Script 3rd Edition

PropertyName : NumericLiteral的产生式按以下方式进行评估:

  1. 形成NumericLiteral的值。
  2. 返回ToString(Result(1))。

1
你如何证明数字被强制转换为字符串,而不是反过来? - Pacerier
3
这段内容摘自ECMA Script规范第3版第42页:PropertyName: NumericLiteral的生成过程如下:
  1. 形成NumericLiteral的值。
  2. 返回ToString(Result(1))。
- Matty F
+1 不过,展示第五版的规则也是很好的,以便彻底解决问题。此外,这并没有展示/涵盖 obj [array] 或类似情况。(那是一条用法规则,而不是语法产生式。) - user166390
好的,只是为了确保,每当我们执行array[1]时,它实际上被转换为array["1"]吗? - Pacerier

5

嗯,这里是我的回答——主要是因为我对其他(正确)答案中的参考资料不满意——方括号内的属性名称表达式始终会被强制转换为字符串,而且这种行为在规范中有明确定义。因此,根据引文的解释不同,它可能被认为是误导和/或不正确。

然而,这句话并没有假设x [42]x ["42"]是不同的;它陈述了——有误导性地排除了其他的基元和细节——只有字符串和数字在正常属性解析下才能作为“哈希键”(实际上是属性名称)可用,在这个意义上,这句话是可以被认为是正确的。

这些规则来自标准 ECMA-262 ECMAScript 语言规范第五版(2009 年 12 月)。

来自“11.2.1 属性访问器”章节(省略生产规则):

生产 MemberExpression: MemberExpression [ Expression ] 的评估方法如下:
  1. 让 baseReference 为评估 MemberExpression 的结果。
  2. 让 baseValue 为 GetValue(baseReference)。
  3. 让 propertyNameReference 为评估 Expression 的结果。
  4. 让 propertyNameValue 为 GetValue(propertyNameReference)。
  5. 调用 CheckObjectCoercible(baseValue)。
  6. 让 propertyNameString 为 ToString(propertyNameValue)。
  7. 如果正在评估的语法生产包含在严格模式代码中,则让 strict 为 true,否则让 strict 为 false。
  8. 返回类型为 Reference 的值,其基本值为 baseValue,引用名称为 propertyNameString,严格模式标志为strict。

5

键总是字符串。这意味着您不能使用对象实例的标识作为键。

FlashActionScript 3中(与AS 2不同,使用强类型运行时),有一个使用严格相等比较键的Dictionary对象,因此您可以使用对象实例本身作为键(以及数字、字符串等)。

如果您想在JavaScript中做同样的事情,那将会很困难,因为您必须生成自己的唯一对象ID并将它们附加到您想要跟踪的每个对象上。有人建议向Object类添加原型函数,但这将不必要地增加每个对象的开销。无论如何,您都需要通过函数调用为对象分配可跟踪的ID,该函数将递增的静态数字分配给唯一属性,例如“__objectid__”。

那么可以考虑创建一个类似于字典的类,带有Add(key, value)等方法,但它必须将字符串、数字和对象存储在三个不同的内部哈希中,以确保"3"不会与数字3或id为3的对象发生冲突。 如果键的类型为对象且尚未分配id,则add方法必须自动分配__objectid__。 即使完成了所有这些步骤,您也无法使用括号访问字典,除非JavaScript中存在一些用于属性赋值的钩子,我不知道。


2
这里有一个 functor Array。在使用 函数式编程 范式时非常有用。
javascript:
    alert(["Using  ",window.navigator.userAgent] );
    FunctorRA=[]; f=function(){return f}; g=function(x){return x};
    FunctorRA[f]=43;
    FunctorRA[g(function(){})]="generic";
    FunctorRA[g(g)]="idempotent";
    alert(FunctorRA);
    for (i in FunctorRA)
        alert([ "Functor[ ", i,
                "]'s\n\n value is \n\n", FunctorRA[i]].join(""));

显示:

Using  ,Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100423
    Ubuntu/10.04 (lucid) Firefox/3.6.3

一个空的alert,然后:

Functor[ function () {
    return f;
}]'s

 value is

43

等等。

须知:

  • alert( FunctorRA ) 显示 .toString() 不会枚举非数字索引
  • FunctorRA 是数组 "衣服" 中的通用对象
  • 没有直接的 . 语法等效项(即使使用字符串 eval 强制转换)。有关如何嵌入函数名称中的冒号的详细信息,请参见JavaScript 中的动态函数名称,即使它通常是分隔符以语法上划分初始化器属性、标签、?:(条件表达式)等。类似的问题也存在于 . 中,需要转义所有语法上重要的 JavaScript 字符代码,例如 (){}\n ., 等。 [] 构造有效地为括号内包含的表达式执行此操作,整体上,可能通过延迟将字符串评估为 String 类型的对象来实现。这类似于事实 43.x=2 是被禁止的,但如果 43 被表示为 Number 类型的对象,则 (43).x=243["x"]=2 则不会出错。 (证明:javascript:alert( [ (43).x=2, 43["x"]=2 ] ) 显示 2,2,但 javascript:alert(43.x=2) 会生成错误)。

0
JavaScript的内置对象确实提供了哈希表功能...
是的,有点像。对象在定义上只是键值对的集合(其中键是字符串或符号)。然而,由于许多人将其用作查找表,大多数引擎会选择使用类似哈希表的数据结构来内部实现对象(在某些情况下)。所以是的,你可以将对象用作哈希表(但不能保证引擎在内部实际上使用哈希表)。
你的键可以是字符串或数字...
不是的。当用作对象键时,数字将被转换为字符串,就像任何其他值一样。因此,你可以将对象用作数字和字符串的哈希表,但不能同时使用两者。
  { "1": true }[1] // true

如果您需要一个用于任意/混合值的哈希表,MapSet将是更好的选择,特别是因为它们保证O(1)的查找时间。

0

是的,键可以是数字。实际上,规范为对象和数组都使用相同的通用映射函数。


3
你的意思是这些数字完全没有被强制转换吗? - Pacerier

0

好的,由于哈希函数将字符串哈希成一个几乎唯一的字节集合,可以将其解释为整数,并且线性搜索期间使用整数比较是低级别和快速的,因此您用于关键字的所有内容都会被转换为字符串(JavaScript对象是哈希表)。您可以将对象和数组字符串化以将它们用作关键字。关键字的大小可以无限制地增长。但要注意,因为JavaScript对象是无序的。

昨天和今天,我一直在努力理解这个领域中出现的问题,并写下了这些解决方案。第一个是哈希表的自定义实现,JavaScript使用对象作为关键字。在那里,一切都以通俗易懂的方式解释...

另一个是对第一个的升级,它是一个确定性JSON字符串化,可以按字母顺序排列属性字符串化对象:JavaScript确定性对象字符串化

来看看吧 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接