【问题标题】:Why does JavaScript convert an array of one string to a string, when used as an object key? [duplicate]当用作对象键时,为什么 JavaScript 将一个字符串的数组转换为字符串? [复制]
【发布时间】:2019-09-15 06:50:17
【问题描述】:

我遇到了一个场景,其中 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

【问题讨论】:

  • 这可以通过不使用数组作为键来防止。
  • 防止这种情况的一种简单方法是使用Map 而不是对象。
  • 仅仅是因为JavaScript在用作对象键时将每个对象转换为字符串?键始终必须是字符串(或符号)。
  • 这不仅仅发生在单字符串数组中。唯一让它们与众不同的是它们的字符串转换会导致它们中包含的相同字符串。但是您也可以尝试使用a[b] = 1 和b = ['foo', 'bar'] 并得到a['foo,bar'] == 1。
  • 有趣的事实,数组只是奇异的对象,所以以下都产生 1:[1][0],[1]["0"]。使用对象时,所有键都转换为字符串。这是您可能希望使用 Map 将某物的真实值存储为键的原因之一

标签: javascript


【解决方案1】:

所有对象键都是字符串,因此它最终会将您放在[] (Bracket notation) 中的所有内容都转换为字符串,如果它是一个表达式,它会计算表达式并将其值转换为字符串并用作键

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

看看这个例子就明白了,这里[a]最终使用a.toString()转换一个toString,然后将它设置为b对象的key

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])

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      为什么 JavaScript 在用作键时会转换 ['foo'] -> 'foo'?
      有人知道原因吗?

      任何时候对于 JavaScript 为何以一种可能出乎意料的方式运行存在混淆,那么查看语言定义是准确找出发生了什么的可靠方法。

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

      首先,您需要找到与阵列访问相关的区域。不过它是语言术语。

      12.3.2.1 Runtime Semantics: Evaluation
      MemberExpression : MemberExpression [ Expression ]

      ...
      3. 令 propertyNameReference 为评估 Expression 的结果。
      4. 让propertyNameValue 为? GetValue(propertyNameReference).
      6. 让propertyKey为? ToPropertyKey(propertyNameValue)。

      所以,这里发生的是您正在使用带有表达式的[] 访问您的数组(成员表达式)。

      为了使用[] 访问,将对表达式求值,然后调用 GetValue。然后将调用 ToPropertyKey。

      1. propertyNameReference = 评估表达式b = b
      2. propertyNameValue = GetValue(propertyNameReference) = ['foo']
      3. propertyKey = ToPropertyKey(propertyNameValue) = 'foo'

      在我们的情况下,ToPropertyKey 会导致ToPrimitive,然后是ToOrdinaryPrimitive,这表明我们应该在参数上调用“toString”(在我们的例子中是['foo'])。

      这就是实现发挥作用的地方。在执行方面,

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

      当数组中只有一个值时,结果就是那个值。

      如何预防?

      这是当前的实施方式。为了改变这一点,您必须更改默认实现,使用检测来阻止调用,或者使用指导来阻止调用。

      指导

      在您的代码中记录并强制执行调用机制。这可能并不总是可能的。不过,期望程序员不使用数组调用属性访问至少是合理的。

      检测

      这将取决于当前环境。使用最新的 JavaScript 迭代,您可以使用类型强制来确保属性访问是数字或字符串。打字稿使这变得相当容易(here is a good example)。它基本上只需要将访问权限定义为:

      function ArrayAccess(value: string | number) {
      

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

      默认实现

      更改默认实现是一个糟糕的主意。它很可能会导致各种重大更改,并且不应该这样做。但是,为了完整起见,这就是它的样子。我主要展示这个,所以如果你在某个地方看到它然后用火杀死它(或者如果它附近没有蜘蛛,或者检查一些代码来修复它),你希望能认出它。

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

      更改实例实现也不是什么好主意。 @Code Maniac in a separate answer 涵盖了这一点。 “在实际场景中,你永远不应该这样做”@Code Maniac 表示,我也同意这一点。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 2022-11-10
          • 1970-01-01
          • 2013-04-19
          • 1970-01-01
          • 2018-09-25
          • 1970-01-01
          • 2010-11-15
          • 2022-01-23
          • 1970-01-01
          相关资源
          最近更新 更多