如果我们把它分开,混乱就等于:
++[[]][+[]]
+
[+[]]
在JavaScript中,+[] === 0
是成立的。+
会将某些东西转换为数字,在这种情况下,它将转化为+""
或0
(请参见下面的规范细节)。++
的优先级高于 +
):++[[]][0]
+
[0]
因为 [[]][0]
的意思是:从 [[]]
中获取第一个元素,所以以下语句成立:
[[]][0]
返回内部的数组([]
)。由于引用的原因,不能说 [[]][0] === []
,但我们可以称内部的数组为A
以避免这种错误的表示法。
在其操作数之前使用++
表示“增加1并返回增加后的结果”。所以++[[]][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"
)。Number +
String ===
String。
"1" + "0" === "10" // Yay!
+[]
的规范细节如下:+[]
转换为字符串,因为+
运算符是这样规定的:ToNumber()
定义如下:ToPrimitive()
定义如下:[[DefaultValue]]
定义如下:.toString
定义如下:+[]
可以简化为+""
,因为[].join() === ""
。+
定义如下:""
,ToNumber
定义如下:+"" === 0
,因此+[] === 0
。true
。0 == ""
将返回true
(经过类型转换后相同),但是0 === ""
将返回false
(类型不同)。 - pimvdb1 + [0]
,而不是"1" + [0]
,因为前缀(++
)操作符总是返回一个数字。参考链接:http://bclary.com/2004/11/07/#a-11.4.4 - Tim Down++[[]][0]
确实返回1
,但++[]
会抛出一个错误。这很引人注目,因为它看起来好像++[[]][0]
归结为++[]
。你是否有任何想法,为什么++[]
会抛出错误,而++[[]][0]
却不会? - pimvdbPutValue
调用中(在 ES3 术语下为 8.7.2)。PutValue
需要一个引用,而 []
作为一个单独的表达式不会产生引用。包含变量引用的表达式(比如我们之前定义了 var a = []
,那么 ++a
就可以工作),或者对象属性访问(比如 [[]][0]
),会产生一个引用。简单地说,前缀运算符不仅会产生一个值,还需要一个位置来存放该值。 - Tim Downvar a = []; ++a
后,a
的值为1。执行++[[]][0]
后,由[[]]
创建的数组现在只包含索引为0的数字1。++
需要一个引用来完成此操作。 - Tim Down++[ [] ][+[]] === 1
+[] === 0
++[ [] ][0] === 1
[ +[] ]
等于 [ 0 ]
然后我们有一个字符串拼接:
1 + String([ 0 ]) === 10
++[[]][+[]]+[+[]]
可能看起来相当令人生畏和晦涩,但实际上可以相对容易地将其分解为单独的表达式。以下是我为了清晰而添加的括号。我可以向您保证它们没有改变任何东西,但如果您想要验证,请自由阅读分组运算符。因此,该表达式可以更清晰地写成:( ++[[]][+[]] ) + ( [+[]] )
将其分解,我们可以通过观察得出+[]
的值为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"
这个表达式的值相同,但稍微小一些
+!![]+''+(+[])
因此,它的求值结果为:
+(true) + '' + (0)
1 + '' + 0
"10"
现在你已经了解了那个,试试这个:
_=$=+[],++_+''+$
"10"
。 - ADJenks++[[]][+[]]+[+[]]
^^^
|
v
++[[]][+[]]+[0]
^^^
|
v
++[[]][0]+[0]
^^^^^^^
|
v
++[]+[0]
^^^
|
v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"
+
操作符通过 .valueOf()
强制将非数字操作数转换为数字。如果该方法不返回数字,则会调用 .toString()
方法。
我们可以通过以下方式进行验证:
const x = [], y = [];
x.valueOf = () => (console.log('x.valueOf() has been called'), y.valueOf());
x.toString = () => (console.log('x.toString() has been called'), y.toString());
console.log(`+x -> ${+x}`);
+[]
相当于把空字符串 ""
强制转换为数字,即 0
。
如果操作数中有一个字符串,则 +
会进行字符串拼接。
++[]+"0"
是无效的,因为第一部分 ++[]
会导致错误。然而,如果有一个索引,比如 ++[][0]
,那么它是允许的。 - qrsngky+[]的值为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
,因为一元运算符 +
将对象强制转换为数字。
+'' or +[] evaluates 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
不等同于""
。首先提取元素,然后通过++
进行转换。 - PointedEars
+[]
将一个空数组转换为0
... 然后就会浪费一个下午时间... ;) - deceze!
将数字转换为布尔值。你可以使用+
将布尔值或字符串转换为数字。从那里开始,你需要弄清楚哪些值会返回真值(强制转换为true或1),哪些值会返回假值(强制转换为false或0)。 - Mariejsfuck: 用6个字符编写任何JavaScript代码:[]()!+
。 - VainMan