为什么++ [[]] [+ []] + [+ []]返回字符串“10”?
这是有效的,并返回JavaScript中的字符串"10"
(更多示例):
++[[]][+[]]+[+[]]
为什么? 这里发生了什么?
如果我们分裂它,混乱等于:
++[[]][+[]]
+
[+[]]
在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"
,因为它是用一个元素连接一个数组。 连接将连接由,
分隔的元素。 用一个元素,你可以推断出这个逻辑将导致第一个元素本身。
所以,最后我们得到(数字+字符串=字符串):
1
+
"0"
=== "10" // Yay!
+[]
规格细节:
这是一个相当迷宫,但要做+[]
,首先它被转换为一个字符串,因为这就是+
说的:
11.4.6一元+运算符
一元+运算符将其操作数转换为数字类型。
生产UnaryExpression:+ UnaryExpression的计算方法如下:
让expr是评估UnaryExpression的结果。
返回ToNumber(GetValue(expr))。
ToNumber()
表示:
目的
应用以下步骤:
让primValue为ToPrimitive(输入参数,提示字符串)。
返回ToString(primValue)。
ToPrimitive()
说:
目的
返回对象的默认值。 通过调用对象的[[DefaultValue]]内部方法来检索对象的默认值,并传递可选的提示PreferredType。 本规范针对8.12.8中的所有本机ECMAScript对象定义[[DefaultValue]]内部方法的行为。
[[DefaultValue]]
说:
8.12.8 [[DefaultValue]](提示)
当使用提示字符串调用O的[[DefaultValue]]内部方法时,将执行以下步骤:
让toString是使用参数“toString”调用对象O的[[Get]]内部方法的结果。
如果IsCallable(toString)为true,那么,
一个。 让str是调用toString的[[Call]]内部方法的结果,其中O作为此值和一个空参数列表。
湾 如果str是原始值,则返回str。
数组的.toString
表示:
15.4.4.2 Array.prototype.toString()
当调用toString方法时,将执行以下步骤:
让数组成为在此值上调用ToObject的结果。
让func成为调用参数“join”的[[Get]]数组内部方法的结果。
如果IsCallable(func)为false,那么让func成为标准的内置方法Object.prototype.toString(15.2.4.2)。
返回调用func提供数组的[[Call]]内部方法的结果作为此值和空参数列表。
所以+[]
归结为+""
,因为[].join() === ""
。
再次, +
被定义为:
11.4.6一元+运算符
一元+运算符将其操作数转换为数字类型。
生产UnaryExpression:+ UnaryExpression的计算方法如下:
让expr是评估UnaryExpression的结果。
返回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.prototype
的toString()
或valueOf()
方法中的任何一个都会更改表达式的结果,因为如果转换对象转换为原始值。 例如,以下
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
......制作"NaNfoo"
。 为什么会发生这种情况只能作为读者的练习...