[英]why does this “4 | 2 | 4 | 1 | 10” return 15 in JavaScript?
[英]Why does ++[[]][+[]]+[+[]] return the string "10"?
如果我們把它分開,混亂就等於:
++[[]][+[]]
+
[+[]]
在 JavaScript 中, +[] === 0
是正確的。 +
將某些內容轉換為數字,在這種情況下,它將歸結為+""
或0
(請參閱下面的規范詳細信息)。
因此,我們可以簡化它( ++
優先於+
):
++[[]][0]
+
[0]
因為[[]][0]
意思是:從[[]]
獲取第一個元素,所以:
[[]][0]
返回內部數組 ( []
)。 由於引用,說[[]][0] === []
,但讓我們調用內部數組A
以避免錯誤的表示法。
++
在其操作數之前表示“加一並返回加后的結果”。 所以++[[]][0]
等價於Number(A) + 1
(或+A + 1
)。
同樣,我們可以將混亂簡化為更清晰的內容。 讓我們用[]
代替A
:
(+[] + 1)
+
[0]
在+[]
可以將數組強制轉換為數字0
,需要先將其強制轉換為字符串,也就是""
。 最后,添加1
,結果為1
。
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
讓我們進一步簡化它:
1
+
[0]
此外,這在 JavaScript 中也是正確的: [0] == "0"
,因為它連接了一個具有一個元素的數組。 加入將連接由,
分隔的元素。 對於一個元素,您可以推斷出此邏輯將生成第一個元素本身。
在這種情況下, +
看到兩個操作數:一個數字和一個數組。 它現在試圖將兩者強制為同一類型。 首先,數組被強制轉換為字符串"0"
,接下來,數字被強制轉換為字符串 ( "1"
)。 數字+
字符串===
字符串。
"1" + "0" === "10" // Yay!
+[]
規格詳情:
這是一個相當迷宮,但要做到+[]
,首先將其轉換為字符串,因為這就是+
所說的:
11.4.6 一元+運算符
一元 + 運算符將其操作數轉換為 Number 類型。
產生式 UnaryExpression : + UnaryExpression 的計算方式如下:
讓 expr 是計算 UnaryExpression 的結果。
返回 ToNumber(GetValue(expr))。
ToNumber()
說:
目的
應用以下步驟:
令 primValue 為 ToPrimitive(輸入參數,提示字符串)。
返回 ToString(primValue)。
ToPrimitive()
說:
目的
返回對象的默認值。 通過調用對象的 [[DefaultValue]] 內部方法,傳遞可選提示 PreferredType 來檢索對象的默認值。 [[DefaultValue]] 內部方法的行為由本規范為 8.12.8 中的所有原生 ECMAScript 對象定義。
[[DefaultValue]]
說:
8.12.8 [[默認值]](提示)
當使用hint String調用O的[[DefaultValue]]內部方法時,采取如下步驟:
令 toString 是使用參數“toString”調用對象 O 的 [[Get]] 內部方法的結果。
如果 IsCallable(toString) 為真,則
一種。 令 str 為調用 toString 的 [[Call]] 內部方法的結果,其中 O 作為 this 值和一個空參數列表。
灣如果 str 是原始值,則返回 str。
數組的.toString
表示:
15.4.4.2 Array.prototype.toString ( )
當調用 toString 方法時,采取以下步驟:
讓 array 是對 this 值調用 ToObject 的結果。
讓 func 是調用帶有參數“join”的數組的 [[Get]] 內部方法的結果。
如果 IsCallable(func) 為 false,則讓 func 成為標准的內置方法 Object.prototype.toString (15.2.4.2)。
返回調用 func 提供數組的 [[Call]] 內部方法的結果作為 this 值和空參數列表。
所以+[]
歸結為+""
,因為[].join() === ""
。
同樣, +
定義為:
11.4.6 一元+運算符
一元 + 運算符將其操作數轉換為 Number 類型。
產生式 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"
。 為什么會發生這種情況留給讀者作為練習......
讓我們簡單點:
++[[]][+[]]+[+[]] = "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"
這個評估結果相同但小一點
+!![]+''+(+[])
所以評估為
+(true) + '' + (0)
1 + '' + 0
"10"
所以現在你明白了,試試這個:
_=$=+[],++_+''+$
+[] 計算結果為 0 [...] 然后將它與任何東西相加(+ 運算)將數組內容轉換為其字符串表示形式,該字符串表示由用逗號連接的元素組成。
任何其他像獲取數組索引(具有比 + 操作更高的優先級)的東西都是有序的,沒什么有趣的。
也許將表達式計算為沒有數字的"10"
的最短方法是:
+!+[] + [+[]] // "10"
-~[] + [+[]] // "10"
+!+[]
:
+[]
被評估為0
。!0
被評估為true
。+true
被評估為1
。-~[]
與-(-1)
,被評估為1
。[+[]]
:
+[]
被評估為 0[0]
是具有單個元素0
的數組。 然后,JS 計算1 + [0]
,一個Number + Array表達式。 然后 ECMA 規范起作用: +
運算符通過調用ToPrimitive和ToString抽象操作將兩個操作數轉換為字符串。 如果表達式的兩個操作數都只是數字,則它作為加法函數運行。 訣竅是數組很容易將它們的元素強制轉換為連接的字符串表示形式。
一些例子:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
[] + [] // ""
[1] + [2] // "12"
{} + {} // "[object Object][object Object]" ¹
{a:1} + {b:2} // "[object Object][object Object]" ¹
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
¹:請注意,每一行都在表達式上下文中進行計算。 第一個{
... }
是一個對象文字,而不是一個塊,就像在語句上下文中的情況一樣。 在 REPL 中,您可能會看到{} + {}
導致NaN
,因為大多數 REPL 在語句上下文中運行; 這里,第一個{}
是一個block ,代碼等價於{}; +{};
{}; +{};
,最后的表達式語句(其值成為完成記錄的結果)是NaN
因為一元+
將對象強制轉換為數字。
+'' 或 +[] 求值為 0。
++[[]][+[]]+[+[]] = 10 ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 1+0 10
++[[]][+[]]+[+[]]
^^^
|
v
++[[]][+[]]+[0]
^^^
|
v
++[[]][0]+[0]
^^^^^^^
|
v
++[]+[0]
^^^
|
v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"
+
運算符通過.valueOf()
任何非數字操作數。 如果那不返回數字,則調用.toString()
。
我們可以簡單地驗證這一點:
const x = [], y = []; x.valueOf = () => (console.log('x.valueOf() has been called'), y.valueOf()); x.toString = () => (console.log('x.toString() has been called'), y.toString()); console.log(`+x -> ${+x}`);
所以+[]
與將""
強制轉換為0
的數字相同。
如果任何操作數是字符串,則+
連接。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.