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

IT技术 javascript syntax
2021-01-23 23:10:21

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

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

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

6个回答

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

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

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

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

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

因为[[]][0]意味着:从 中获取第一个元素[[]],所以:

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

++在其操作数之前的意思是“加一并返回增加的结果”。So++[[]][0]等价于Number(A) + 1(或+A + 1)。

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

(+[] + 1)
+
[0]

+[]将数组强制转换为 number 之前0,需要先将其强制为字符串,也就是"", 。最后,1相加,结果为1

  • (+[] + 1) === (+"" + 1)
  • (+"" + 1) === (0 + 1)
  • (0 + 1) === 1

让我们进一步简化它:

1
+
[0]

此外,这在 JavaScript: 中也是正确的[0] == "0",因为它使用一个元素连接一个数组。Joining 将连接由 分隔的元素,对于一个元素,您可以推断出此逻辑将生成第一个元素本身。

在这种情况下,+看到两个操作数:一个数字和一个数组。它现在试图将两者强制为同一类型。首先,数组被强制转换为字符串"0",接下来,数字被强制转换为字符串 ( "1")。数字+字符串===字符串

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

规格详情+[]

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

11.4.6 一元+运算符

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

产生式 UnaryExpression : + UnaryExpression 计算如下:

  1. 让 expr 是计算 UnaryExpression 的结果。

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

ToNumber() 说:

目的

应用以下步骤:

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

  2. 返回到字符串(primValue)。

ToPrimitive() 说:

目的

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

[[DefaultValue]] 说:

8.12.8 [[默认值]](提示)

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

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

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

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

如果 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 一元+运算符

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

产生式 UnaryExpression : + UnaryExpression 计算如下:

  1. 让 expr 是计算 UnaryExpression 的结果。

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

ToNumber定义为""

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

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

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

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

1+[0].toString() = 10
===而不是写会更清楚=>吗?
2021-03-13 23:10:21
@MateenUlhaq 也许它会更清楚,但它不会完全正确,因为[+[]] === [0]在 JS 中评估为 false。
2021-03-23 23:10:21

以下内容改编自回答此问题博客文章,我在此问题仍然关闭时发布了该文章。链接指向 ECMAScript 3 规范(的 HTML 副本),该规范仍然是当今常用 Web 浏览器中 JavaScript 的基准。

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

该表达式++[[]][+[]]+[+[]]最初可能看起来相当气势和晦涩,但实际上相对容易分解为单独的表达式。为了清楚起见,我在下面简单地添加了括号;我可以向你保证他们没有改变,但如果你想验证,那么请随意阅读分组运算符所以,表达式可以更清楚地写成

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

分解这一点,我们可以通过观察+[]评估为来简化0为了让自己明白为什么这是真的,请查看一元 + 运算符并遵循稍微曲折的路径,最终ToPrimitive将空数组转换为空字符串,然后最终0ToNumber转换为空字符串我们现在可以替换0的每个实例+[]

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

已经更简单了。至于++[[]][0],它是前缀增量运算符( ++)的组合,一个数组字面量定义了一个具有单个元素的数组,该数组本身就是一个空数组 ( [[]]) 和一个属性访问器( [0]) 调用由数组字面量定义的数组。

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

那么,有什么作用++[[]][0]呢?好吧,通过与 类似的逻辑+[],内部数组被转换为0并且这个值增加1以给我们一个最终值10外部数组中的 property 值更新为1并且整个表达式的计算结果为1

这给我们留下了

1 + [0]

...这是加法运算符的简单使用两个操作数首先被转换为原始值,如果其中一个原始值是字符串,则执行字符串连接,否则执行数字加法。[0]转换为"0",因此使用字符串连接,产生"10".

最后一点,可能不是很明显的是,覆盖toString()valueOf()方法中的任何一个Array.prototype都会改变表达式的结果,因为在将对象转换为原始值时,如果存在,就会检查和使用这两个方法。例如,以下

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"

这个评估结果相同但小一点

+!![]+''+(+[])
  • [] - 是一个被转换的数组,当你添加或减去它时会转换为 0,因此 +[] = 0
  • ![] - 评估为假,因此 !![] 评估为真
  • +!![] - 将 true 转换为计算结果为 true 的数值,因此在这种情况下为 1
  • +'' - 将一个空字符串附加到表达式中,导致数字被转换为字符串
  • +[] - 计算结果为 0

所以评估为

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

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

_=$=+[],++_+''+$
_=$=+[],++_+''+$ -> _=$=0,++_+''+$ -> _=0,$=0,++_+''+$ -> ++0+''+0 -> 1+''+0 -> '10' // Yei :v
2021-03-12 23:10:21
那么不,它仍然评估为“10”。然而,这是以不同的方式做到的。尝试在 chrome 之类的 javascript 检查器中评估它。
2021-04-07 23:10:21
这个评估结果相同,但它甚至比你的还要小: "10"
2021-04-09 23:10:21