这是有效的,并在JavaScript中返回字符串“10”(此处有更多示例):

控制台日志(++[[]][+[]]+[+[]])

为什么?这里发生了什么?


当前回答

将表达式求值为不带数字的“10”的最短可能方法是:

+!+[] + [+[]] // "10"
-~[] + [+[]]  // "10"

解释

+!+[]:+[]的值为0。!0的计算结果为true。+true被评估为1。-~[]与求值为1的-(-1)相同。[+[]]:+[]评估为0[0]是具有单个元素0的数组。

然后,JS计算1+[0],一个Number+Array表达式。然后ECMA规范起作用:+运算符通过调用ToPrimitive和ToString抽象操作将两个操作数转换为字符串。如果表达式的两个操作数都是数字,则它作为加法函数运行。诀窍是数组很容易将它们的元素强制转换为连接的字符串表示。

一些示例:

1 + {}            // "1[object Object]"
1 + []            // "1"
1 + new Date()    // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
[] + []           // ""
[1] + [2]         // "12"
{} + {}           // "[object Object][object Object]" ¹
{a:1} + {b:2}     // "[object Object][object Object]" ¹
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"

¹:请注意,每一行都是在表达式上下文中计算的。第一个{…}是一个对象文本,而不是一个块,就像语句上下文中的情况一样。在REPL中,您可能会看到{}+}生成NaN,因为大多数REPL在语句上下文中操作;这里,第一个{}是一个块,代码相当于{}+{};, 最终表达式语句(其值成为完成记录的结果)为NaN,因为一元+将对象强制为数字。

其他回答

如果我们将其拆分,那么混乱就等于:

++[[]][+[]]
+
[+[]]

在JavaScript中,+[]===0.+将某物转换为数字,在这种情况下,它将降为+“”或0(请参阅下面的规范详细信息)。

因此,我们可以简化它(++优先于+):

++[[]][0]
+
[0]

因为[[]][0]的意思是:从[[]]中获取第一个元素,所以:

[[]][0]返回内部数组([])。由于引用,说[[]][0]==[]是错误的,但让我们调用内部数组A以避免错误的表示法。

++在其操作数之前表示“递增一并返回递增的结果”。因此,++[[]][0]等同于数字(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一元+运算符一元+运算符将其操作数转换为Number类型。生产UnaryExpression:+UnaryExpression的计算如下:让expr是对UnaryExpression求值的结果。返回到编号(GetValue(expr))。

ToNumber()表示:

对象应用以下步骤:让primValue为ToPrimitive(输入参数,提示字符串)。返回到字符串(primValue)。

ToPrimitive()表示:

对象返回对象的默认值。通过调用对象的[[DefaultValue]]内部方法并传递可选提示PreferredType来检索对象的默认值。[[DefaultValue]]内部方法的行为由8.12.8中所有本机ECMAScript对象的规范定义。

[[DefaultValue]]表示:

8.12.8[[DefaultValue]](提示)当使用提示字符串调用O的[[DefaultValue]]内部方法时,将采取以下步骤:让toString是用参数“toString”调用对象O的[[Get]]内部方法的结果。如果IsCallable(toString)为真,a.让str是调用toString的[[Call]]内部方法的结果,此值为O,参数列表为空。b.如果str是基元值,则返回str。

数组的.toString表示:

15.4.4.2阵列.原型.字符串()调用toString方法时,将执行以下步骤:让数组是对该值调用ToObject的结果。让func是用参数“join”调用数组的[[Get]]内部方法的结果。如果IsCallable(func)为false,则让func为标准内置方法Object.prototype.toString(15.2.4.2)。返回调用func提供数组的[[Call]]内部方法的结果,作为this值和空参数列表。

所以+[]归结为+“”,因为[].jjoin()==“”。

同样,+定义为:

11.4.6一元+运算符一元+运算符将其操作数转换为Number类型。生产UnaryExpression:+UnaryExpression的计算如下:让expr是对UnaryExpression求值的结果。返回到编号(GetValue(expr))。

“”的ToNumber定义为:

StringNumericLiteral:::[empty]的MV为0。

因此+“”===0,因此+[]===0。

++[ [] ][+[]] === 1+[] === 0++[ [] ][0] === 1[+[]]是[0]

然后我们有一个字符串连接:

1+字符串([0])==10

让我们简单一点:

++[[]][+[]]+[+[]] = "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"

以下内容改编自我在这个问题尚未解决时发布的一篇回答这个问题的博文。链接是到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”。为什么会发生这种情况,留给读者一个练习。。。

++[[]][+[]]+[+[]]
             ^^^
             |
             v
++[[]][+[]]+[0]
       ^^^
       |
       v
++[[]][0]+[0]
  ^^^^^^^
  |
  v
++[]+[0]
     ^^^
     |
     v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"

+运算符通过.valueOf()强制任何非数字操作数。如果不返回数字,则调用.toString()。

我们可以通过以下方式进行验证:

常量x=[],y=[];x.valueOf=()=>(console.log('x.valueOf()已被调用'),y.valueOf));x.toString=()=>(console.log('x.toString()已被调用'),y.toString(());console.log(`+x->${+x}`);

所以+[]与将“”强制为0相同。

如果任何操作数是字符串,则+连接。