为什么 ++[[]][+[]]+[+[]] 返回字符串“10”?
如果我们把它分开,混乱就等于:
++[[]][+[]]
+
[+[]]
在 JavaScript 中,确实如此+[] === 0
。+
将某些内容转换为数字,在这种情况下,它将归结为+""
或0
(请参阅下面的规范详细信息)。
因此,我们可以简化它(++
优先于+
):
++[[]][0]
+
[0]
因为[[]][0]
意味着:从 中获取第一个元素[[]]
,所以:
[[]][0]
返回内部数组 ( []
)。由于引用,说 是错误的[[]][0] === []
,但让我们调用内部数组A
以避免错误的表示法。
++
在其操作数之前的意思是“加一并返回增加的结果”。So++[[]][0]
等价于Number(A) + 1
(或+A + 1
)。
同样,我们可以将混乱简化为更清晰的内容。让我们[]
换回A
:
(+[] + 1)
+
[0]
在+[]
将数组强制转换为 number 之前0
,需要先将其强制为字符串,也就是""
, 。最后,1
相加,结果为1
。
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
让我们进一步简化它:
1
+
[0]
此外,这在 JavaScript: 中也是正确的[0] == "0"
,因为它使用一个元素连接一个数组。Joining 将连接由 分隔的元素,
。对于一个元素,您可以推断出此逻辑将生成第一个元素本身。
在这种情况下,+
看到两个操作数:一个数字和一个数组。它现在试图将两者强制为同一类型。首先,数组被强制转换为字符串"0"
,接下来,数字被强制转换为字符串 ( "1"
)。数字+
字符串===
字符串。
"1" + "0" === "10" // Yay!
规格详情+[]
:
这是一个相当迷宫,但要做到+[]
这一点,首先将其转换为字符串,因为这+
就是说:
11.4.6 一元+运算符
一元 + 运算符将其操作数转换为 Number 类型。
产生式 UnaryExpression : + UnaryExpression 计算如下:
让 expr 是计算 UnaryExpression 的结果。
返回 ToNumber(GetValue(expr))。
ToNumber()
说:
目的
应用以下步骤:
令 primValue 为 ToPrimitive(输入参数,提示字符串)。
返回到字符串(primValue)。
ToPrimitive()
说:
目的
返回对象的默认值。通过调用对象的 [[DefaultValue]] 内部方法,传递可选的提示 PreferredType 来检索对象的默认值。[[DefaultValue]] 内部方法的行为由本规范为 8.12.8 中的所有原生 ECMAScript 对象定义。
[[DefaultValue]]
说:
8.12.8 [[默认值]](提示)
当使用hint String调用O的[[DefaultValue]]内部方法时,采取如下步骤:
让 toString 是调用对象 O 的 [[Get]] 内部方法的结果,参数为“toString”。
如果 IsCallable(toString) 为真,则
一个。令 str 为调用 toString 的 [[Call]] 内部方法的结果,其中 O 作为 this 值和一个空参数列表。
湾 如果 str 是原始值,则返回 str。
该.toString
数组的说:
15.4.4.2 Array.prototype.toString ( )
当调用 toString 方法时,采取以下步骤:
令数组成为对 this 值调用 ToObject 的结果。
令 func 是调用带有参数“join”的数组的 [[Get]] 内部方法的结果。
如果 IsCallable(func) 为 false,则让 func 成为标准的内置方法 Object.prototype.toString (15.2.4.2)。
返回调用 func 提供数组的 [[Call]] 内部方法的结果作为 this 值和空参数列表。
所以+[]
归结为+""
,因为[].join() === ""
。
同样,+
定义为:
11.4.6 一元+运算符
一元 + 运算符将其操作数转换为 Number 类型。
产生式 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将空数组转换为空字符串,然后最终0
由ToNumber转换为空字符串。我们现在可以替换0
的每个实例+[]
:
( ++[[]][0] ) + [0]
已经更简单了。至于++[[]][0]
,它是前缀增量运算符( ++
)的组合,一个数组字面量定义了一个具有单个元素的数组,该数组本身就是一个空数组 ( [[]]
) 和一个属性访问器( [0]
) 调用由数组字面量定义的数组。
所以,我们可以简化[[]][0]
为 just []
,我们有++[]
,对吧?事实上,情况并非如此,因为评估++[]
会引发错误,这最初看起来可能令人困惑。但是,稍微考虑一下 的性质就++
可以清楚地说明这一点:它用于增加变量(例如++i
)或对象属性(例如++obj.count
)。它不仅计算出一个值,它还将该值存储在某处。在 的情况下++[]
,它无处放置新值(无论它是什么),因为没有对要更新的对象属性或变量的引用。在规范方面,这由内部PutValue操作涵盖,该操作由前缀增量运算符调用。
那么,有什么作用++[[]][0]
呢?好吧,通过与 类似的逻辑+[]
,内部数组被转换为0
并且这个值增加1
以给我们一个最终值1
。0
外部数组中的 property 值更新为1
并且整个表达式的计算结果为1
。
这给我们留下了
1 + [0]
...这是加法运算符的简单使用。两个操作数首先被转换为原始值,如果其中一个原始值是字符串,则执行字符串连接,否则执行数字加法。[0]
转换为"0"
,因此使用字符串连接,产生"10"
.
最后一点,可能不是很明显的是,覆盖toString()
或valueOf()
方法中的任何一个Array.prototype
都会改变表达式的结果,因为在将对象转换为原始值时,如果存在,就会检查和使用这两个方法。例如,以下
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... 产生"NaNfoo"
. 为什么会发生这种情况留给读者作为练习......
让我们简单点:
++[[]][+[]]+[+[]] = "10"
var a = [[]][+[]];
var b = [+[]];
// so a == [] and b == [0]
++a;
// then a == 1 and b is still that array [0]
// when you sum the var a and an array, it will sum b as a string just like that:
1 + "0" = "10"
这个评估结果相同但小一点
+!![]+''+(+[])
- [] - 是一个被转换的数组,当你添加或减去它时会转换为 0,因此 +[] = 0
- ![] - 评估为假,因此 !![] 评估为真
- +!![] - 将 true 转换为计算结果为 true 的数值,因此在这种情况下为 1
- +'' - 将一个空字符串附加到表达式中,导致数字被转换为字符串
- +[] - 计算结果为 0
所以评估为
+(true) + '' + (0)
1 + '' + 0
"10"
所以现在你明白了,试试这个:
_=$=+[],++_+''+$