为什么++ [[]] [+ []] + [+ []]返回字符串“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.prototypetoString()valueOf()方法中的任何一个都会更改表达式的结果,因为如果转换对象转换为原始值。 例如,以下

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

    ......制作"NaNfoo" 。 为什么会发生这种情况只能作为读者的练习...

    链接地址: http://www.djcxy.com/p/1307.html

    上一篇: Why does ++[[]][+[]]+[+[]] return the string "10"?

    下一篇: Why Does OAuth v2 Have Both Access and Refresh Tokens?