这是有效的,并在JavaScript中返回字符串“10”(此处有更多示例):
控制台日志(++[[]][+[]]+[+[]])
为什么?这里发生了什么?
这是有效的,并在JavaScript中返回字符串“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”。为什么会发生这种情况,留给读者一个练习。。。
其他回答
让我们简单一点:
++[[]][+[]]+[+[]] = "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![]-计算结果为false,因此!![]计算结果为true+!![]-将true转换为计算结果为true的数值,因此在本例中为1+“”-将空字符串附加到表达式,使数字转换为字符串+[]-计算结果为0
因此其计算结果为
+(true) + '' + (0)
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相同。
如果任何操作数是字符串,则+连接。
将表达式求值为不带数字的“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,因为一元+将对象强制为数字。