为什么 ++[[]][+[]]+[+[]] 返回字符串“10”?
如果我们将其拆分,则混乱情况将等于:
++[[]][+[]]
+
[+[]]
在 JavaScript 中,
+[] === 0
是正确的。
+
将某个对象转换为数字,在这种情况下,它将归结为
+""
或
0
(请参阅下面的规范详细信息)。
因此,我们可以简化它(
++
优先于
+
):
++[[]][0]
+
[0]
因为
[[]][0]
表示:从
[[]]
获取第一个元素,所以以下情况是正确的:
[[]][0]
返回内部数组(
[]
)。由于引用,
[[]][0] === []
的说法是错误的,但让我们调用内部数组
A
以避免错误的表示法。
++
在其操作数之前表示“增加一并返回增加的结果”。因此
++[[]][0]
相当于
Number(A) + 1
(或
+A + 1
)。
同样,我们可以将混乱简化为更清晰的内容。让我们将
[]
替换回
A
:
(+[] + 1)
+
[0]
在
+[]
可以将数组强制转换为数字
0
之前,需要先将其强制转换为字符串,即
""
,再次。最后,添加
1
,结果为
1
。
-
(+[] + 1) === (+"" + 1)
-
(+"" + 1) === (0 + 1)
-
(0 + 1) === 1
让我们进一步简化它:
1
+
[0]
此外,这在 JavaScript 中是正确的:
[0] == "0"
,因为它连接了一个具有一个元素的数组。连接将连接由
,
分隔的元素。对于一个元素,您可以推断出此逻辑将产生第一个元素本身。
在这种情况下,
+
看到两个操作数:一个数字和一个数组。现在它尝试将两者强制转换为同一类型。首先,数组被强制转换为字符串
"0"
,接下来,数字被强制转换为字符串 (
"1"
)。
数字
+
字符串
===
字符串
。
"1" + "0" === "10" // Yay!
+[]
的规范详细信息:
这是一个相当复杂的过程,但要执行
+[]
,首先要将其转换为字符串,因为
+
表示:
11.4.6 Unary + Operator
The unary + operator converts its operand to Number type.
The production UnaryExpression : + UnaryExpression is evaluated as follows:
Let expr be the result of evaluating UnaryExpression.
Return ToNumber(GetValue(expr)).
ToNumber()
表示:
Object
Apply the following steps:
Let primValue be ToPrimitive(input argument, hint String).
Return ToString(primValue).
ToPrimitive()
表示:
Object
Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.
[[DefaultValue]]
表示:
8.12.8 [[DefaultValue]] (hint)
When the [[DefaultValue]] internal method of O is called with hint String, the following steps are taken:
Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".
If IsCallable(toString) is true then,
a. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.
b. If str is a primitive value, return str.
数组的
.toString
说:
15.4.4.2 Array.prototype.toString ( )
When the toString method is called, the following steps are taken:
Let array be the result of calling ToObject on the this value.
Let func be the result of calling the [[Get]] internal method of array with argument "join".
If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2).
Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list.
因此
+[]
归结为
+""
,因为
[].join() === ""
。
同样,
+
定义为:
11.4.6 Unary + Operator
The unary + operator converts its operand to Number type.
The production UnaryExpression : + UnaryExpression is evaluated as follows:
Let expr be the result of evaluating UnaryExpression.
Return ToNumber(GetValue(expr)).
ToNumber
对于
""
定义为:
The MV of StringNumericLiteral ::: [empty] is 0.
因此
+"" === 0
,因此
+[] === 0
。
-
++[ [] ][+[]] === 1
-
+[] === 0
-
++[ [] ][0] === 1
-
-
[ +[] ]
是[ 0 ]
然后我们有一个字符串连接:
1 + String([ 0 ]) === 10
以下内容改编自我在问题尚未解决时发布的一篇回答此问题的 博客文章 。链接指向 ECMAScript 3 规范(的 HTML 副本),该规范仍然是当今常用 Web 浏览器中 JavaScript 的基准。
首先,需要说明的是:这种表达式永远不会出现在任何(合理的)生产环境中,并且只能作为一种练习,以了解读者对 JavaScript 的了解程度。JavaScript 运算符在类型之间隐式转换的一般原则很有用,一些常见的转换也是如此,但本例中的许多细节并不适用。
表达式
++[[]][+[]]+[+[]]
最初可能看起来相当气势汹汹且晦涩难懂,但实际上可以相对轻松地分解为单独的表达式。下面我只是为了清晰起见添加了括号;我可以向您保证它们不会改变任何东西,但如果您想验证这一点,请随时阅读有关
分组运算符
的信息。因此,表达式可以更清楚地写为
( ++[[]][+[]] ) + ( [+[]] )
分解一下,我们可以通过观察
+[]
的计算结果为
0
来简化。要弄清楚为什么这是正确的,请查看
一元 + 运算符
,然后按照稍微曲折的路径,最终以
ToPrimitive
将空数组转换为空字符串,然后最终通过
ToNumber
将其转换为
0
。现在我们可以用
0
替换
+[]
的每个实例:
( ++[[]][0] ) + [0]
已经更简单了。至于
++[[]][0]
,它是
前缀增量运算符
(
++
)、
数组文字
(定义一个具有单个元素的数组,该元素本身是一个空数组 (
[[]]
))和在数组文字定义的数组上调用的
属性访问器
(
[0]
) 的组合。
因此,我们可以将
[[]][0]
简化为
[]
,我们有
++[]
,对吗?事实上,情况并非如此,因为评估
++[]
会引发错误,这最初可能看起来令人困惑。但是,稍微思考一下
++
的性质,就会明白这一点:它用于增加变量(例如
++i
)或对象属性(例如
++obj.count
)。它不仅评估为一个值,还将该值存储在某处。对于
++[]
,它无处放置新值(无论它是什么),因为没有对要更新的对象属性或变量的引用。在规范术语中,这由内部
PutValue
操作涵盖,该操作由前缀增量运算符调用。
那么,
++[[]][0]
会做什么呢?好吧,按照与
+[]
类似的逻辑,内部数组将转换为
0
,并且此值将增加
1
,从而得到最终值
1
。外部数组中属性
0
的值将更新为
1
,并且整个表达式的计算结果为
1
。
这给我们留下了
1 + [0]
... 这是
加法运算符
的简单用法。两个操作数首先
转换为原始值
,如果任一原始值是字符串,则执行字符串连接,否则执行数字加法。
[0]
转换为
"0"
,因此使用字符串连接,产生
"10"
。
最后,可能不是立即显而易见的是,覆盖
Array.prototype
的
toString()
或
valueOf()
方法之一将改变表达式的结果,因为在将对象转换为原始值时会检查并使用它们(如果存在)。例如,以下
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... 产生
"NaNfoo"
。为什么会发生这种情况就留给读者去思考吧……