一个关于JavaScript类型转换的问题
( [ ][ [] ] + [ ] )[ -~{} ] + ( {} + {} )[ -~{} - ~{} ]
这篇文章主要通过一条完全由符号组成的语句来分析JavaScript是如何执行的。
首先,将式子分为两部分:
1. ( [][ [] ] + [])[ -~{}]
2. ( {} + {} )[ -~{} - ~{} ]
第一部分
先来看第一部分。
这一部分从总体看是一个通过下标访问特定的位置的表达式,在这里简写为
( X ) [ index ]
其中
- X: [ ][ [] ]+ [ ]
- index:-~{}
先看X:
X在这里分为两部分,声明两个标识符lval和rval,令
- lval => [ ][ [] ]
- rval => [ ]
分析AST树可以看出,这两部分都是表达式,并且通过二元操作符"+"连接。
查阅ECMAScript规范关于The Addition Operator一节的内容。
-
lval+rval,实际将转换为ToPrimitve(lval)+ToPrimitve(rval)
-
对于lval,它是一个通过下标访问数组元素的表达式,其中数组为空数组,下标为“[]”。根据规范Integer Indexed Exotic Objects一节内容,数组的索引必须是整数值。
-
因此要将[]转换为数值类型。首先调用规范方法ToNumber,对[]进行转换。即ToNumber([])。
-
查阅规范,因为[]不是基本类型,所以先调用ToPrimitive,转换为基本类型后再调用ToNumber。
-
查阅ToPrimitvie,对于数组类型,会先调用数组的valueOf方法,如果不能返回基本类型,再接着调用toString方法。如果都失败,会抛出TypeError错误。
-
对于数组,因为调用valueOf返回原数组本身,所以接着调用toString方法。查阅 Array.prototype.toString ( ),该方法对调用Array.prototype.join ( separator )方法,将数组拼接成字符串,在这里传入的拼接符separator 参数为空,取默认值为","。
根据规范关于Array.prototype.join ( separator )的内容,“[]”是一个空数组,该方法的结果返回空字符串"",长度为0。因此ToPrimitve([])返回空字符串""
-
将 [] 转换为基本类型后,接着需要对ToPrimitve([])的返回值调用ToNumber,根据规范ToNumber("")返回 0。
所以lval转换成 [ ][0],对于数组访问规则,因为[]是一个空数组,所以对任何位置的访问都返回undefined。 -
所以ToPrimitive(lval)的值为undefined。
-
接着看rval部分。通过上面对lval部分的求值可知,ToPrimitive([])返回值是""。
-
所以lval+rval,转换为undefined+"",根据二元操作符加法规则,当任何一方是sring类型时,操作符两边都将转换为string类型,所以undefined转换为string为“undefined”
lval+rval=“undefined”+""=“undefined”
即X部分结果为"undefined"
index部分:-~{}
index部分分为三个小块,
- 一元操作符: -
- 一元操作符: ~
- 表达式: {}
根据优先级,先求值~{},令其结果为rst1,再求值 -rst1
求值过程如下:
- 根据规范Bitwise NOT Operator ( ~ )一节内容,求值~{},实际会通过ToInt32 ( argument )方法将传入值转换为数值.
- 在ToInt32 内部,首先要调用ToNumber将{}转换为Number类型。
- 根据上面提到过的流程,过程为ToPrimitve({}),再将其结果调用ToNumber。
- 根据规范,ToPrimitive({}),实际会最终调用toString方法。根据Object.prototype.toString ( )一节内容,toString返回字符串"[object Object]",所以ToPrimitive({})值为字符串"[object Object]"
- 所以接着调用ToNumber("[object Object]"),根据规范,返回值为NaN。
- 根据ToInt32 方法描述,当ToNumber返回值为 NaN, +0, ‑0, +∞, 或者 ‑∞时, 一律返回+0.
- 在这里,~{} 实际已经转换为~0。而~0结果为-1.所以~{}结果为-1
- 接着计算-(-1),返回1。
- 所以index部分的值为1
因此第一部分(X)[index]转换为(“undefined”)[1],是通过索引访问字符串的操作,返回单个字符"n"。
第二部分
下面再看第二部分
({} + {})[-~{} - ~{}]
这部分从总体看依旧是通过下标访问一个特定的位置,
在这里简写为
(Z)[index2]
其中
- X:{} + {}
- index:-~{} - ~{}
Z部分:
在这里需要注意,当{}作为语句的开头,它会优先被解释为语句块,而非对象表达式。
所以,我们可以看到{}+[]结果为0,正是因为如此。
但在这里,因为Z位于括号里面,所以这里{}会被解释为表达式
-
首先根据二元操作符"+",操作符两侧的表达式会被调用ToPrimitive方法转换为基本值。
即,ToPrimitve({})+ToPrimitve({}) -
根据上面已经得出的结论,ToPrimitve({})+ToPrimitve({})会被转换为:
“[object Object]”+"[object Object]"
所以{} + {} => “[object Object][object Object]”
-
因此Z部分值为"[object Object][object Object]"
index2部分:
-
根据上面已经得出的结论,~{}被转换为-1,所以-~{} - ~{}转换为-(-1)-(-1),值为2.
-
所以第二部分变为["[object Object][object Object]"][2],依旧是通过数字索引访问字符串特定字符,返回结果为"b"。
结论
结合第一部分和第一部分,上式变为"n"+“b”=>“nb”