为什么 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。
- propertyNameReference = 评估表达式
b
= b
- propertyNameValue = 获取值(propertyNameReference) =
['foo']
- 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指出:"在实际场景中你永远不应该这样做",我也同意这个观点。
Map
而不是对象。 - georgb = ['foo','bar']
进行a[b] = 1
,并将得到a['foo,bar'] == 1
。 - Bergilet a = {1:'a'}; console.log(typeof Object.keys(a)[0] === 'string' )
。 - Code Maniac[1][0]
,[1]["0"]
。当使用对象时,所有键都转换为字符串。这是您可能需要使用Map
将某物的真值存储为键的一个原因。 - Dan