为什么++ [[]] [+ []] + [+ []]返回字符串"10"?

Joh*_*nGa 1613 javascript syntax

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

console.log(++[[]][+[]]+[+[]])
Run Code Online (Sandbox Code Playgroud)

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

pim*_*vdb 2032

如果我们把它分开,这个混乱就等于:

++[[]][+[]]
+
[+[]]
Run Code Online (Sandbox Code Playgroud)

在JavaScript中,确实如此+[] === 0.+将某些东西转换为数字,在这种情况下,它将归结为+""0(参见下面的规范细节).

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

++[[]][0]
+
[0]
Run Code Online (Sandbox Code Playgroud)

因为[[]][0]意味着:从第一个元素中获取[[]],确实如此:

  • [[]][0]返回内部数组([]).由于引用它是错误的说[[]][0] === [],但让我们调用内部数组A以避免错误的表示法.
  • ++,因为++[[]][0]意味着'递增一'.
  • Number(A) + 1; 换句话说,它总是一个数字(+A + 1不一定会返回一个数字,而[]总是这样 - 感谢Tim Down指出这一点).

同样,我们可以将混乱简化为更清晰的东西.让我们替换A回来+[]:

(+[] + 1)
+
[0]
Run Code Online (Sandbox Code Playgroud)

在JavaScript中,这也是正确的:0因为""(加入一个空数组),所以:

  • 1,和
  • 1,和
  • (+[] + 1) === (+"" + 1)

让我们进一步简化它:

1
+
[0]
Run Code Online (Sandbox Code Playgroud)

此外,在JavaScript中也是如此:(+"" + 1) === (0 + 1)因为它使用一个元素连接数组.连接将连接分隔的元素(0 + 1) === 1.使用一个元素,您可以推断出该逻辑将导致第一个元素本身.

所以,最后我们得到(number + string = string):

"1" + "0" === "10" // Yay!
Run Code Online (Sandbox Code Playgroud)

规格细节[0] == "0":

这是一个非常迷宫,但要做到,这一点,首先它被转换为字符串,因为这是+说:

11.4.6一元+算子

一元+运算符将其操作数转换为数字类型.

生产UnaryExpression:+ UnaryExpression的计算方法如下:

  1. 设expr是评估UnaryExpression的结果.

  2. 返回ToNumber(GetValue(expr)).

"0" 说:

宾语

应用以下步骤:

  1. 让primValue为ToPrimitive(输入参数,提示字符串).

  2. 返回ToString(primValue).

"1" 说:

宾语

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

+ 说:

8.12.8 [[DefaultValue]](提示)

当使用提示字符串调用O的[[DefaultValue]]内部方法时,将执行以下步骤:

  1. 设toString是使用参数"toString"调用对象O的[[Get]]内部方法的结果.

  2. 如果IsCallable(toString)为真,那么,

一个.令str为调用toString的[[Call]]内部方法的结果,其中O为此值,且为空参数列表.

湾 如果str是原始值,则返回str.

===数组的说:

15.4.4.2 Array.prototype.toString()

调用toString方法时,将执行以下步骤:

  1. 令数组是在此值上调用ToObject的结果.

  2. 让func成为使用参数"join"调用数组的[[Get]]内部方法的结果.

  3. 如果IsCallable(func)为false,则让func成为标准的内置方法Object.prototype.toString(15.2.4.2).

  4. 返回调用func提供数组的[[Call]]内部方法的结果作为此值和空参数列表.

所以+[]归结为+[],因为+.

同样,ToNumber()定义为:

11.4.6一元+算子

一元+运算符将其操作数转换为数字类型.

生产UnaryExpression:+ UnaryExpression的计算方法如下:

  1. 设expr是评估UnaryExpression的结果.

  2. 返回ToNumber(GetValue(expr)).

ToPrimitive()定义为[[DefaultValue]]:

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

所以.toString,从而+[].

  • 部分原因是不正确的.表达式归结为"1 + [0]",而不是"1"+ [0]`,因为前缀(`++`)运算符总是返回一个数字.见http://bclary.com/2004/11/07/#a-11.4.4 (39认同)
  • @pimvdb:执行`var a = []后执行; ++ a`,`a`是1.在执行`++ [[]] [0]`之后,由`[[]]`表达式创建的数组现在只包含索引0处的数字1. +`需要一个参考来做到这一点. (13认同)
  • @pimvdb:我很确定问题出在前缀操作中的`PutValue`调用(在ES3术语,8.7.2中).`PutValue`需要一个Reference,而`[]`作为一个表达式本身不会产生一个Reference.包含变量引用的表达式(比如我们之前定义的`var a = []`然后`++ a`起作用)或对象的属性访问(例如`[[]] [0]`)会生成一个引用.简单来说,前缀运算符不仅会生成一个值,还需要某个位置来放置该值. (11认同)
  • @harper:它是严格的相等检查器,即如果值和类型相同,它只返回`true`.`0 ==""`返回`true`(类型转换后相同),但是`0 ===""`是`false`(不是同一类型). (8认同)
  • @Tim Down:你完全正确.我正试图纠正这个问题,但在尝试这样做的时候,我发现了别的东西.我不确定这是怎么回事.`++ [[]] [0]`确实返回`1`,但`++ []`会抛出错误.这很值得注意,因为它看起来像`++ [[]] [0]`可以归结为`++ []`.你可能知道为什么`++ []`会抛出错误,而`++ [[]] [0]`却没有? (6认同)
  • nit:一个`instanceof Array`(比如`[]`)将_never_` ===`一个`Array`而不是它自己,因为严格比较运算符只是将lref与rref(即每个实例的内存位置)进行比较.我不建议甚至暗示这一点.您可能还会在答案中注意到JS'一元`+`运算符优先于任何算术表达式结果或字符串连接(取决于上下文)与`++`或`+`这就是为什么`+ []`首先得到评估. (4认同)

She*_*hef 121

++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Run Code Online (Sandbox Code Playgroud)

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

1+[0].toString() = 10
Run Code Online (Sandbox Code Playgroud)

  • 写`===`而不是`=>`会更清晰吗? (5认同)
  • @MateenUlhaq也许它会更清楚,但它不会完全正确,因为 `[+[]] === [0]` 在 JS 中计算结果为 false。 (2认同)

Tim*_*own 60

以下内容改编自一篇博客文章,回答了我在此问题仍未结束时发布的问题.链接是ECMAScript 3规范的(HTML副本),仍然是当今常用Web浏览器中JavaScript的基线.

首先,评论:这种表达式永远不会出现在任何(理智的)生产环境中,并且只是作为一种练习用于读者如何知道JavaScript的脏边缘.JavaScript运算符在类型之间隐式转换的一般原则是有用的,一些常见的转换也是如此,但在这种情况下的大部分细节都不是.

该表达式++[[]][+[]]+[+[]]最初可能看起来相当模糊和模糊,但实际上相对容易分解为单独的表达式.下面我简单地添加括号以便清楚; 我可以向你保证他们什么都不会改变,但如果你想验证那么你可以随意阅读有关分组操作员的信息.因此,表达式可以更清楚地写成

( ++[[]][+[]] ) + ( [+[]] )
Run Code Online (Sandbox Code Playgroud)

打破这一点,我们可以通过观察+[]评估来简化0.为了满足自己为什么这是真的,请查看一元+运算符,然后按照稍微弯曲的路径,最后使用ToPrimitive将空数组转换为空字符串,最后0ToNumber转换为空字符串.我们现在可以替换0以下每个实例+[]:

( ++[[]][0] ) + [0]
Run Code Online (Sandbox Code Playgroud)

已经更简单了.至于++[[]][0],这是前缀增量operator(++)的组合,一个数组文字定义一个数组,单个元素本身是一个空数组([[]])和一个属性访问器([0])在数组文字定义的数组上调用.

所以,我们可以简化[[]][0]到公正[],我们拥有++[],对吗?实际上,情况并非如此,因为评估++[]会引发错误,这可能最初看起来令人困惑.然而,稍微考虑一下它的性质++就会明确:它用于增加变量(例如++i)或对象属性(例如++obj.count).它不仅会评估一个值,还会在某个地方存储该值.在这种情况下++[],它无处可放新值(无论它是什么),因为没有引用要更新的对象属性或变量.在规范方面,这由内部PutValue操作覆盖,该操作由前缀增量运算符调用.

那么,做++[[]][0]什么呢?那么,通过类似的逻辑+[],内部数组被转换为,0并且该值递增1以给出最终值1.0外部数组中的属性值更新为1,整个表达式的计算结果为1.

这让我们失望了

1 + [0]
Run Code Online (Sandbox Code Playgroud)

...这是加法运算符的简单用法.两个操作数首先转换为基元,如果原始值是字符串,则执行字符串连接,否则执行数字加法.[0]转换为"0",所以使用字符串连接,生成"10".

作为最后的一点,可能不会立即显而易见的是,覆盖其中一个toString()或多个valueOf()方法Array.prototype将改变表达式的结果,因为在将对象转换为原始值时,如果存在,则检查并使用它们.例如,以下内容

Array.prototype.toString = function() {
  return "foo";
};
++[[]][+[]]+[+[]]
Run Code Online (Sandbox Code Playgroud)

......产生"NaNfoo".为什么会发生这种情况留给读者练习...


小智 23

让我们简单一点:

++[[]][+[]]+[+[]] = "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"
Run Code Online (Sandbox Code Playgroud)


小智 13

这个评估为相同但略小

+!![]+''+(+[])
Run Code Online (Sandbox Code Playgroud)
  • [] - 是一个转换后的数组,当你加或减时转换为0,因此+ [] = 0
  • ![] - 计算结果为false,因此!! []计算结果为true
  • + !! [] - 将true转换为计算结果为true的数值,因此在本例中为1
  • +'' - 在表达式中附加一个空字符串,使数字转换为字符串
  • + [] - 计算结果为0

所以评估为

+(true) + '' + (0)
1 + '' + 0
"10"
Run Code Online (Sandbox Code Playgroud)

所以现在你有了,试试这个:

_=$=+[],++_+''+$
Run Code Online (Sandbox Code Playgroud)

  • 这个的计算结果相同,但比你的还要小:`“10”` (4认同)

cus*_*der 10

++[[]][+[]]+[+[]]
             ^^^
             |
             v
++[[]][+[]]+[0]
       ^^^
       |
       v
++[[]][0]+[0]
  ^^^^^^^
  |
  v
++[]+[0]
     ^^^
     |
     v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"
Run Code Online (Sandbox Code Playgroud)

运算+符通过 强制任何非数字操作数.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}`);
Run Code Online (Sandbox Code Playgroud)

因此与强制转换为 的数字+[]相同。""0

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


Esk*_*t0n 7

+ []计算为0 [...]然后对任何事物进行求和(+运算)将数组内容转换为包含用逗号连接的元素组成的字符串表示.

任何其他类似于获取数组的索引(具有比+操作更重要的优先级)是有序的并且没有什么有趣的.


Arm*_*ian 5

也许评估"10"没有数字的表达式的最短方法是:

+!+[] + [+[]] // "10"
-~[] + [+[]]  // "10"
Run Code Online (Sandbox Code Playgroud)

解释

  • +!+[]
    • +[]被评估为0
    • !0被评估为true
    • +true被评估为1
  • -~[]-(-1)被评估为的相同1
  • [+[]]
    • +[] 被评估为 0
    • [0]是一个具有单个元素的数组0

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

一些例子:

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]"
Run Code Online (Sandbox Code Playgroud)

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


Ali*_*eza 5

一步一步地,+将值转换为一个数字,如果你添加到一个空数组+[]......因为它是空的并且等于0,它将

所以从那里开始,现在看看你的代码,它是++[[]][+[]]+[+[]]......

他们之间还有加号++[[]][+[]]+[+[]]

所以这些[+[]]将返回,[0]因为它们有一个空数组,该数组被转换为0另一个数组......

所以想象一下,第一个值是一个二维数组,里面有一个数组......所以[[]][+[]]将等于[[]][0]which 将返回[]......

最后++将其转换并增加到1......

所以你可以想象,1+"0"将是"10"......

为什么返回字符串“10”?