一個關於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”