如果我们把它分开,这个混乱就等于:
++[[]][+[]]
+
[+[]]
在 JavaScript 中, +[] === 0
确实如此。 +
将某些东西转换为数字,在这种情况下,它将降为+""
或0
(参见下面的规范细节)。
因此,我们可以简化它( ++
优先于+
):
++[[]][0]
+
[0]
因为[[]][0]
表示:从[[]]
获取第一个元素,所以:
[[]][0]
返回内部数组( []
)。由于引用说[[]][0] === []
,但让我们调用内部数组A
以避免错误的表示法。 ++[[]][0] == A + 1
,因为++
表示 '递增 1'。 ++[[]][0] === +(A + 1)
; 换句话说,它总是一个数字( +1
不一定返回一个数字,而++
总是这样 - 感谢 Tim Down 指出这一点)。 同样,我们可以将混乱简化为更清晰的内容。让我们用[]
代替A
:
+([] + 1)
+
[0]
在 JavaScript 中,这也是正确的: [] + 1 === "1"
,因为[] == ""
(加入一个空数组),所以:
+([] + 1) === +("" + 1)
,和+("" + 1) === +("1")
,和+("1") === 1
让我们进一步简化它:
1
+
[0]
此外,在 JavaScript 中也是如此: [0] == "0"
,因为它正在使用一个元素连接数组。加入将通过串联分离的元素,
。使用一个元素,您可以推断出该逻辑将导致第一个元素本身。
所以,最后我们得到(number + string = string):
1
+
"0"
=== "10" // Yay!
+[]
规格详情:
这是一个迷宫,但要做+[]
,首先它被转换为字符串,因为这就是+
所说的:
11.4.6 一元 + 算子
一元 + 运算符将其操作数转换为数字类型。
生产 UnaryExpression:+ UnaryExpression 的计算方法如下:
设 expr 是评估 UnaryExpression 的结果。
返回 ToNumber(GetValue(expr))。
ToNumber()
说:
宾语
应用以下步骤:
设 primValue 为 ToPrimitive(输入参数,提示字符串)。
返回 ToString(primValue)。
ToPrimitive()
说:
宾语
返回 Object 的默认值。通过调用对象的 [[DefaultValue]] 内部方法,传递可选提示 PreferredType 来检索对象的默认值。对于 8.12.8 中的所有本机 ECMAScript 对象,此规范定义了 [[DefaultValue]] 内部方法的行为。
[[DefaultValue]]
说:
8.12.8 [[DefaultValue]](提示)
当使用提示字符串调用 O 的 [[DefaultValue]] 内部方法时,将执行以下步骤:
设 toString 是使用参数 “toString” 调用对象 O 的 [[Get]] 内部方法的结果。
如果 IsCallable(toString)为真,那么,
一个。令 str 为调用 toString 的 [[Call]] 内部方法的结果,其中 O 为此值,且为空参数列表。
湾如果 str 是原始值,则返回 str。
数组的.toString
说:
15.4.4.2 Array.prototype.toString()
调用 toString 方法时,将执行以下步骤:
令数组是在此值上调用 ToObject 的结果。
让 func 成为使用参数 “join” 调用数组的 [[Get]] 内部方法的结果。
如果 IsCallable(func)为 false,则让 func 成为标准的内置方法 Object.prototype.toString(15.2.4.2)。
返回调用 func 提供数组的 [[Call]] 内部方法的结果作为此值和空参数列表。
所以+[]
归结为+""
,因为[].join() === ""
。
同样, +
定义为:
11.4.6 一元 + 算子
一元 + 运算符将其操作数转换为数字类型。
生产 UnaryExpression:+ UnaryExpression 的计算方法如下:
设 expr 是评估 UnaryExpression 的结果。
返回 ToNumber(GetValue(expr))。
ToNumber
的定义为""
为:
StringNumericLiteral ::: [empty] 的 MV 为 0。
所以+"" === 0
,因此+[] === 0
。
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
然后我们有一个字符串连接
1+[0].toString() = 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"
。为什么会发生这种情况留给读者练习...