你可能在網上見過有人用 幾個不同的字符寫的各種稀奇古怪的 JavaScript 代碼,雖然看起來奇怪,但是能正常運行!比如這個:
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
你猜運行結果是什么?你可以自己去控制台試一下。
看起來很神奇,但這到底是怎么回事呢?
事實上,你幾乎可以用下面這 6 個字符寫出任意的 JavaScript 程序:
[]()!+
很多人都知道這個技巧,但是沒有多少開發人員知道它到底是如何工作的。今天,我們就來看看它背后的執行原理。我們的目標是用這幾個字符來寫出字符串“self”。姑且用這個字符串向 Self 語言致敬,JavaScript 的靈感來源之一就是它。
基本原理
我們之所以能夠拋開其他字符不用,要歸功於 JavaScript 的類型系統和數據類型轉換機制。
這 6 個字符是這樣各顯神通的:[]可以用來創建數組,!和+可以在數組上執行一些操作,再用()給這些操作分組。
先看一個簡單的數組:
[]
數組前加上!會把它轉成布爾值。數組被認為是真值,因此取非之后變成了false:
![] === false
除非轉換為類似類型,否則無法將不同類型的值加在一起。JavaScript 在進行轉換時遵循一個預定義的規則:
在表達式2 + true中,JavaScript 會將true轉成數字,得到表達式2+1。
在表達式2 + "2"中,JavaScript 會將數字轉成字符串,得到2 + "2" === "22"。
這些轉換規則還不算糟糕,但是對於其他類型,好戲馬上來了。
JavaScript 數組強制轉換
數組相加會轉換成字符串並連接起來。空數組轉換為空字符串,因此將兩個數組相加將得到空字符串。
[] + [] === "" + "" === ""
數組跟其他類型值相加時也一樣:
![] + [] === "false" + "" === "false"
驚不驚喜?我們得到了目標字符串"self"所包含的幾個字符!
如果我們能產生一些數字,就可以按正確的順序提取所需的字符:
"false"[3] === "s"
(![] + [])[3] === "s"
那么,如何生成數字呢?
生成數字
前面提到了,可以把數組轉成布爾值。那如果用加號+把它轉成數字會怎樣?
+[] === ???
JavaScript 會嘗試調用數組的valueOf方法,但是發現不存在這個方法,然后就轉而調用toString() 方法了。因此上面的代碼等效於:
+[] === +""
將字符串轉換為數字將產生以下結果:
+"42" === 42
+"esg" == NaN
+"" === 0
空字符串是一個 false值,跟 null,undefined和數字零類似,因此將其中任何一個轉換為數字都會變成零:
+null === 0
+undefined === 0
+false === 0
+NaN === 0
+"" === 0
因此,將數組轉換為數字需要先將其轉換為字符串,最后轉成 0:
+[] === +"" === 0
第一個數字已經造出來了!我們還需要更多數字,繼續:
!0 === !false
!false === true
!0 === true
將 0 取否就得到一個為真的布爾值。為真的布爾值轉成數字,就是1:
+true === 1
有了 1,自然就可以得到2,所謂道生一,一生二,二生三,三生萬物……
用上面的轉換大法,可以輕松得到我們想要的這些數字:
1 === +true == +(!0) ==== +(!(+[])) === +!+[]
1 === +!+[]
2 === +!+[] +!+[]
3 === +!+[] +!+[] +!+[]
4 === +!+[] +!+[] +!+[] +!+[]
臨門一腳,大功告成
總結下這些規則:
- 數組屬於真值,取否就得到
false:![] // false - 數組相加時會轉換成字符:
[] + [] // "" - 空數組轉成數字得到
0,再去否得到true,再轉成數字得到1:+(!(+[])) === 1
根據這些規則,我們就能得到想要的字符串。看下面這個示意圖就很清楚了:
![] + [] === "false"
+!+[] === 1
(![] + [])[3] + (![] + [])[4] + (![] + [])[2] + (![] + [])[0]
^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^
"false" "false" "false" "false"
^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^
s e l f
最終的表達式就是這樣:
(![] + [])[+!+[]+!+[]+!+[]] +
(![] + [])[+!+[]+!+[]+!+[]+!+[]] +
(![] + [])[+!+[]+!+[]] +
(![] + [])[+[]]
整理下空格和換行,就是一行代碼:
(![]+[])[+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]+!+[]+!+[]]+(![]+[])[+!+[]+!+[]]+(![]+[])[+[]]
現在你應該明白了那些神奇 JavaScript 代碼的原理了吧?發揮你的想象,看還能寫出其他什么來?比如,2020 年剛到,來個 “Happy New Year”?
Anyway,Happy New Year!
更多前端技術干貨盡在微信公眾號:1024譯站

