为什么在JavaScript中,当将一个只包含一个字符串的数组用作对象键时,会将其转换为字符串?

23

我遇到了一个让我有点困惑的 JavaScript 行为场景。

假设我们有一个对象,其中包含两个键 foo 和 bar。

a = { foo: 1, bar: 2 }

然后,我有一个字符串数组,在这种情况下只有一个'foo'。

b = ['foo']

我期望以下内容:

a[b] == undefined
a[b[0]] == 1

但是,以下就是发生的事情:

a[b] == 1
a[b[0]] == 1

为什么 JavaScript 在用作键时将 ['foo'] -> 'foo' 进行转换?

是否有人知道原因?

如何防止这种情况发生?

let a = { foo: 1, bar: 2 }
let b = ['foo']

console.log(a[b] == 1)    // expected a[b] to be undefined
console.log(a[b[0]] == 1)  // expected a[b] to be 1


9
不使用数组作为键可以预防这种情况发生。 - QuentinUK
1
一个简单的防止这种情况发生的方法是使用Map而不是对象。 - georg
8
因为JavaScript在将任何对象用作对象键时都会将其转换为字符串,所以键总是必须是字符串(或符号)。 - Bergi
2
这不仅会发生在单字符串数组上。唯一使它们特殊的是,它们的字符串转换导致与它们包含的相同字符串。但您也可以尝试使用b = ['foo','bar'] 进行 a[b] = 1,并将得到a['foo,bar'] == 1 - Bergi
任何非字符串的对象都将被转换为字符串。例如,let a = {1:'a'}; console.log(typeof Object.keys(a)[0] === 'string' ) - Code Maniac
1
有趣的事实是,数组只是异类对象,因此以下两者都产生1:[1][0][1]["0"]。当使用对象时,所有键都转换为字符串。这是您可能需要使用Map将某物的真值存储为键的一个原因。 - Dan
4个回答

14

所有对象键都是字符串,所以它最终将 [] (方括号表示法) 中放置的任何内容转换为字符串。如果它是一个表达式,它会评估该表达式并将其值转换为字符串,并用作键。

console.log(['foo'].toString())

看这个例子以理解,这里的 [a] 最终会使用 a.toString() 转换为字符串,并将其设置为 b 对象的键。

let a = { a : 1}

let b = {
  [a] : a
}

// object converted to string
console.log(a.toString())

// object built using [] computed property access
console.log(b)


如何停止这个

在实际场景中,您永远不应该这样做,但只是为了说明,您可以拦截或覆盖对象的toString方法,并将值作为字符串返回,其中加上[] :

let a = { foo: 1, bar: 2 }

let b = ['foo']
b.toString = function() {
  let string = this.join(',')
  return "[" + string  + "]"
}

console.log(b.toString())
console.log(a[b])


13
当使用数组作为键时,JavaScript会调用该数组的toString()方法,并尝试将数组的字符串版本作为键来查找。如果您调用['foo'].toString(),则会看到此方法返回"foo"。

3

为什么 JavaScript 在用作键时将 ['foo'] 转换为 'foo'?
是否有人知道原因?

每当对 JavaScript 的行为感到困惑时,查看语言定义是确切了解发生了什么的方法。

https://www.ecma-international.org/ecma-262/10.0/ 是发布时最新的语言定义。

首先,您需要找到与数组访问相关的区域。但它是用语言术语描述的。

12.3.2.1 运行时语义:评估
MemberExpression : MemberExpression [ Expression ]

...
3. 计算 Expression 的结果并将其赋值给 propertyNameReference
4. 将 propertyNameReference 的值获取并赋值给 propertyNameValue。(如果有异常则返回 undefined)
6. 将 propertyNameValue 转换成属性键(property key),并将其赋值给 propertyKey。(如果有异常则抛出TypeError)

这里是使用 [] 访问数组(MemberExpression)并提供一个 Expression。

为了使用 [],需要计算 Expression 并调用 GetValue。然后调用 ToPropertyKey。

  1. propertyNameReference = 评估表达式 b = b
  2. propertyNameValue = 获取值(propertyNameReference) = ['foo']
  3. propertyKey = 转换为属性键(propertyNameValue) = 'foo'

在我们的情况下,转换为属性键会导致ToPrimitive,然后到ToOrdinaryPrimitive,它指出我们应该对参数(在我们的情况下为['foo'])调用“toString”。

这就是实现的地方。在实现方面,

Array对象覆盖了Object的toString方法。对于Array对象,toString方法将数组连接起来并返回一个字符串,其中包含每个数组元素以逗号分隔" MDN - Array toString

当数组中只有一个值时,结果将仅为该值。
如何防止这种情况发生?
这是当前的实现方式。要更改它,您必须更改默认实现、使用检测来防止调用,或使用指导来防止调用。
指导
在您的代码中记录和强制调用机制。这可能并不总是可能的。但至少可以合理地期望程序员不会使用数组调用属性访问。
检测
这将取决于当前的环境。在JavaScript的最新迭代中,您可以使用类型强制确保属性访问为数字或字符串。TypeScript使这变得很容易(这里有一个好例子)。它基本上只需要定义访问为:
function ArrayAccess(value: string | number) {

这将防止任何人将数组用作访问器值。

默认实现

更改默认实现是一个可怕的想法。它很可能会导致各种破坏性变化,不应该这样做。然而,为了完整起见,这里展示一下它的样子。主要是为了让你在某个地方看到它时能够识别出来,然后消灭它(或检查一些代码以修复它,如果它周围没有蜘蛛)。

var arrayToString = [].toString;
Array.prototype.toString = function(){
  if(this.length === 1) return;
  return arrayToString.call(this);
};

更改实例实现也不是一个更好的想法。这在单独的答案@Code Maniac中有所涵盖。@Code Maniac指出:"在实际场景中你永远不应该这样做",我也同意这个观点。

0
当使用数组作为键时,JavaScript会调用该数组的'toString()'方法,然后尝试找到数组的字符串化版本作为键。如果你调用['foo'].toString(),你会看到这个方法返回"foo"。

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