【问题标题】:Why does ++[[]][+[]]+[+[]] return the string "10"?为什么 ++[[]][+[]]+[+[]] 返回字符串“10”?
【发布时间】:2011-11-04 08:30:09
【问题描述】:

这是有效的,并在 JavaScript (more examples here) 中返回字符串 "10"

console.log(++[[]][+[]]+[+[]])

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

【问题讨论】:

  • 首先要了解+[] 将一个空数组转换为0... 然后浪费一个下午... ;)
  • 看看wtfjs.com - 它有很多类似的解释。
  • @deceze,你在哪里学的那种东西?有哪些书?我在 MDN 学 JS,他们不教这些东西
  • @SiddharthThevaril 和你刚才做的一样:有人在某处发布了它,而我碰巧读到了它。

标签: javascript syntax


【解决方案1】:

如果我们将其拆分,则混乱等于:

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

在 JavaScript 中,+[] === 0 是真的。 + 将某些内容转换为数字,在这种情况下,它将归结为 +""0(请参阅下面的规范详细信息)。

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

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

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

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

++ 在其操作数之前表示“加一并返回增加后的结果”。所以++[[]][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")。 数字+字符串===字符串

"1" + "0" === "10" // Yay!

+[] 的规格详情:

这是一个相当迷宫,但要做到+[],首先将其转换为字符串,因为这是+ 所说的:

11.4.6 一元 + 运算符

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

产生式 UnaryExpression : + UnaryExpression 的评估如下:

  1. 设 expr 为计算 UnaryExpression 的结果。

  2. Return ToNumber(GetValue(expr)).

ToNumber() 说:

对象

应用以下步骤:

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

  2. 返回 ToString(primValue)。

ToPrimitive() 说:

对象

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

[[DefaultValue]] 说:

8.12.8 [[DefaultValue]](提示)

当O的[[DefaultValue]]内部方法用hint String调用时,采取如下步骤:

  1. 令 toString 为使用参数“toString”调用对象 O 的 [[Get]] 内部方法的结果。

  2. 如果 IsCallable(toString) 为真,则

一个。令 str 为调用 toString 的 [[Call]] 内部方法的结果,O 为 this 值,参数列表为空。

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

数组的.toString 表示:

15.4.4.2 Array.prototype.toString()

在调用toString方法时,会采取以下步骤:

  1. 令数组为对 this 值调用 ToObject 的结果。

  2. 令 func 为以“join”为参数调用数组的 [[Get]] 内部方法的结果。

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

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

所以+[] 归结为+"",因为[].join() === ""

同样,+ 定义为:

11.4.6 一元 + 运算符

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

产生式 UnaryExpression : + UnaryExpression 的评估如下:

  1. 设 expr 为计算 UnaryExpression 的结果。

  2. Return ToNumber(GetValue(expr)).

ToNumber"" 定义为:

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

所以+"" === 0,因此+[] === 0

【讨论】:

  • @harper:它是严格的相等检查器,即它只返回 true 如果值和类型都相同。 0 == "" 返回true(类型转换后相同),但0 === ""false(类型不同)。
  • 部分内容不正确。该表达式归结为1 + [0],而不是"1" + [0],因为前缀(++) 运算符总是返回一个数字。见bclary.com/2004/11/07/#a-11.4.4
  • @Tim Down:你完全正确。我正在尝试纠正这一点,但在尝试这样做时,我发现了其他东西。我不确定这怎么可能。 ++[[]][0] 确实返回 1,但 ++[] 会引发错误。这很了不起,因为看起来++[[]][0] 确实归结为++[]。您是否知道为什么++[] 会抛出错误而++[[]][0] 不会?
  • @pimvdb:我很确定问题出在前缀操作中的 PutValue 调用中(在 ES3 术语中,8.7.2)。 PutValue 需要引用,而 [] 作为表达式本身不会产生引用。包含变量引用的表达式(比如我们之前定义了var a = [] 然后++a 有效)或对象的属性访问(例如[[]][0])会产生一个引用。简单来说,前缀运算符不仅会产生一个值,它还需要在某个地方放置该值。
  • @pimvdb:所以在执行var a = []; ++a 之后,a 为 1。在执行++[[]][0] 之后,[[]] 表达式创建的数组现在只包含索引 0 处的数字 1。 ++ 需要参考才能执行此操作。
【解决方案2】:
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

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

1+[0].toString() = 10

【讨论】:

  • ===而不是=>不是更清楚吗?
  • @MateenUlhaq 也许会更清楚,但不会完全正确,因为 [+[]] === [0] 在 JS 中的计算结果为 false。
【解决方案3】:

+[] 计算结果为 0 [...] 然后将它与任何东西相加(+ 操作)将数组内容转换为其字符串表示形式,该字符串表示形式由用逗号连接的元素组成。

任何其他像获取数组索引(比 + 操作具有更高的优先级)都是有序的,没有什么有趣的。

【讨论】:

    【解决方案4】:

    以下内容改编自 blog post 回答我在此问题尚未结束时发布的此问题。链接指向 ECMAScript 3 规范(的 HTML 副本),它仍然是当今常用 Web 浏览器中 JavaScript 的基线。

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

    表达式++[[]][+[]]+[+[]] 最初可能看起来相当威严和晦涩,但实际上相对容易分解为单独的表达式。为了清楚起见,下面我只是添加了括号;我可以向您保证,它们没有任何改变,但如果您想验证这一点,请随时阅读有关grouping operator 的信息。所以,表达式可以更清楚地写成

    ( ++[[]][+[]] ) + ( [+[]] )
    

    分解它,我们可以通过观察 +[] 的计算结果为 0 来简化。为了满足自己为什么这是真的,请查看unary + operator 并遵循稍微曲折的路径,最终以ToPrimitive 将空数组转换为空字符串,然后由ToNumber 最终转换为0。我们现在可以将0 替换为+[] 的每个实例:

    ( ++[[]][0] ) + [0]
    

    已经更简单了。至于++[[]][0],它是prefix increment operator (++) 的组合,array literal 定义了一个具有单个元素的数组,该元素本身是一个空数组 ([[]]) 和一个property accessor ([0] ) 在由数组字面量定义的数组上调用。

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

    那么,++[[]][0] 做了什么?好吧,通过与+[] 类似的逻辑,内部数组被转换为0,并且这个值增加了1,最终得到了1。外部数组中属性0 的值更新为1,整个表达式的计算结果为1

    这给我们留下了

    1 + [0]
    

    ...这是addition operator 的简单用法。两个操作数首先是converted to primitives,如果任一原始值是字符串,则执行字符串连接,否则执行数字加法。 [0] 转换为"0",因此使用字符串连接,生成"10"

    作为最后一点,可能不会立即明显的事情是覆盖Array.prototypetoString()valueOf() 方法之一将更改表达式的结果,因为如果存在两者都会被检查和使用将对象转换为原始值时。比如下面的

    Array.prototype.toString = function() {
      return "foo";
    };
    ++[[]][+[]]+[+[]]
    

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

    【讨论】:

      【解决方案5】:

      这个计算结果相同,但有点小

      +!![]+''+(+[])
      
      • [] - 是一个被转换的数组,当你添加或减去它时,它被转换为 0,因此 +[] = 0
      • ![] - 计算结果为假,因此 !![] 计算结果为真
      • +!![] - 将 true 转换为计算结果为 true 的数值,因此在本例中为 1
      • +'' - 将一个空字符串附加到表达式中,从而将数字转换为字符串
      • +[] - 计算结果为 0

      所以是评估为

      +(true) + '' + (0)
      1 + '' + 0
      "10"
      

      现在你明白了,试试这个:

      _=$=+[],++_+''+$
      

      【讨论】:

      • 不,它仍然评估为“10”。但是,这是以不同的方式进行的。尝试在 chrome 之类的 javascript 检查器中评估它。
      • _=$=+[],++_+''+$ -> _=$=0,++_+''+$ -> _=0,$=0, +_+''+$ -> +0+''+0 -> 1+''+0 -> '10' // Yei :v
      • 这个评估结果相同,但比你的还要小:"10"
      【解决方案6】:
      1. 一元加给定字符串转换为数字
      2. 给定字符串的递增运算符转换并递增 1
      3. [] == ''。空字符串
      4. +'' 或 +[] 计算结果为 0。

        ++[[]][+[]]+[+[]] = 10 
        ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
        1+0 
        10
        

      【讨论】:

      • 答案令人困惑/困惑,IOW 错误。 []等同于""。首先提取元素,然后通过++进行转换。
      【解决方案7】:

      让我们简单点:

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

      【讨论】:

        【解决方案8】:

        将表达式计算为不带数字的"10" 的最短方法可能是:

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

        说明

        • +!+[]:
          • +[] 被评估为 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]"
        

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

        【讨论】:

          【解决方案9】:

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

          从那里开始,现在查看您的代码,它是 ++[[]][+[]]+[+[]]...

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

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

          想象一下,第一个值是一个 2 维 数组,其中包含一个数组...所以[[]][+[]] 将等于[[]][0] 将返回[]...

          最后将++转换为1...

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

          【讨论】:

            【解决方案10】:
            ++[[]][+[]]+[+[]]
                         ^^^
                         |
                         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 的数字相同。

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-09-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-08-23
              相关资源
              最近更新 更多