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