协慌网

登录 贡献 社区

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

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

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

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

答案

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

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

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

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

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

因为[[]][0]表示:从[[]]获取第一个元素,所以:

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

同样,我们可以将混乱简化为更清晰的内容。让我们用[]代替A

+([] + 1)
+
[0]

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

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

让我们进一步简化它:

1
+
[0]

此外,在 JavaScript 中也是如此: [0] == "0" ,因为它正在使用一个元素连接数组。加入将通过串联分离的元素, 。使用一个元素,您可以推断出该逻辑将导致第一个元素本身。

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

1
+
"0"

=== "10" // Yay!

+[]规格详情:

这是一个迷宫,但要做+[] ,首先它被转换为字符串,因为这就是+所说的:

11.4.6 一元 + 算子

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

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

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

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

ToNumber()说:

宾语

应用以下步骤:

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

  2. 返回 ToString(primValue)。

ToPrimitive()说:

宾语

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

[[DefaultValue]]说:

8.12.8 [[DefaultValue]](提示)

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

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

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

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

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

数组的.toString说:

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]] 内部方法的结果作为此值和空参数列表。

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

同样, +定义为:

11.4.6 一元 + 算子

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

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

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

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

ToNumber的定义为""为:

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

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

++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

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

1+[0].toString() = 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.prototypetoString()valueOf()方法中的任何一个都将更改表达式的结果,因为在转换时,如果存在则两者都被检查和使用将对象转换为原始值。例如,以下内容

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

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