开发者问题收集

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

2011-08-26
225736

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

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

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

3个回答

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

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

在 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 Unary + Operator

The unary + operator converts its operand to Number type.

The production UnaryExpression : + UnaryExpression is evaluated as follows:

  1. Let expr be the result of evaluating UnaryExpression.

  2. Return ToNumber(GetValue(expr)).

ToNumber() 表示:

Object

Apply the following steps:

  1. Let primValue be ToPrimitive(input argument, hint String).

  2. Return ToString(primValue).

ToPrimitive() 表示:

Object

Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8.

[[DefaultValue]] 表示:

8.12.8 [[DefaultValue]] (hint)

When the [[DefaultValue]] internal method of O is called with hint String, the following steps are taken:

  1. Let toString be the result of calling the [[Get]] internal method of object O with argument "toString".

  2. If IsCallable(toString) is true then,

a. Let str be the result of calling the [[Call]] internal method of toString, with O as the this value and an empty argument list.

b. If str is a primitive value, return str.

数组的 .toString 说:

15.4.4.2 Array.prototype.toString ( )

When the toString method is called, the following steps are taken:

  1. Let array be the result of calling ToObject on the this value.

  2. Let func be the result of calling the [[Get]] internal method of array with argument "join".

  3. If IsCallable(func) is false, then let func be the standard built-in method Object.prototype.toString (15.2.4.2).

  4. Return the result of calling the [[Call]] internal method of func providing array as the this value and an empty arguments list.

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

同样, + 定义为:

11.4.6 Unary + Operator

The unary + operator converts its operand to Number type.

The production UnaryExpression : + UnaryExpression is evaluated as follows:

  1. Let expr be the result of evaluating UnaryExpression.

  2. Return ToNumber(GetValue(expr)).

ToNumber 对于 "" 定义为:

The MV of StringNumericLiteral ::: [empty] is 0.

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

pimvdb
2011-08-26
  • ++[ [] ][+[]] === 1
    • +[] === 0
    • ++[ [] ][0] === 1
  • [ +[] ][ 0 ]

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

1 + String([ 0 ]) === 10

Shef
2011-08-26

以下内容改编自我在问题尚未解决时发布的一篇回答此问题的 博客文章 。链接指向 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.prototypetoString()valueOf() 方法之一将改变表达式的结果,因为在将对象转换为原始值时会检查并使用它们(如果存在)。例如,以下

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

... 产生 "NaNfoo" 。为什么会发生这种情况就留给读者去思考吧……

Tim Down
2011-09-14