一行神奇的javascript代碼
寫本篇文章的緣由是之前群里@墨塵發(fā)了一段js代碼,如下:
- (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
然后讓大家運(yùn)行,出來的結(jié)果讓人有點(diǎn)出乎意料,請(qǐng)看:
太風(fēng)騷了有木有!如果有人詆毀前端瞧不起js的話,那就可以把這段代碼發(fā)給他了~
不過話說回來了,這到底是什么原理呢?為什么一堆符號(hào)運(yùn)算結(jié)果竟然能是兩個(gè)字符,而且恰巧還是個(gè)sb!
其實(shí)靠的是js的類型轉(zhuǎn)化的一些基本原理,本篇就來揭密”sb”是如何煉成的。相信你如果能把這個(gè)理清楚了,以后遇到類型轉(zhuǎn)化之類的題目,就可以瞬間秒殺了。
首先要運(yùn)用到的***個(gè)知識(shí)就是js運(yùn)算符的優(yōu)先級(jí),因?yàn)檫@么長一段運(yùn)算看的人眼花,我們必須得先根據(jù)優(yōu)先級(jí)分成n小段,然后再各個(gè)擊破。優(yōu)先級(jí)的排列如下表:
優(yōu)先級(jí)從高到低:
根據(jù)此規(guī)則,我們把這一串運(yùn)算分為以下16個(gè)子表達(dá)式:
運(yùn)算符用紅色標(biāo)出,有一點(diǎn)可能大家會(huì)意識(shí)不到,其實(shí)中括號(hào)[]也是一個(gè)運(yùn)算符,用來通過索引訪問數(shù)組項(xiàng),另外也可以訪問字符串的子字符,有點(diǎn)類似charAt方法,如:’abcd’[1] // 返回’b'。而且中括號(hào)的優(yōu)先級(jí)還是***的哦。
預(yù)處理結(jié)束,接下來需要運(yùn)用的就是javascript的類型轉(zhuǎn)化知識(shí)了。我們先說說什么情況下需要進(jìn)行類型轉(zhuǎn)化。當(dāng)操作符兩邊的操作數(shù)類型不一致或者不是基本類型(也叫原始類型)時(shí),需要進(jìn)行類型轉(zhuǎn)化。先按運(yùn)算符來分一下類:
- 減號(hào)-,乘號(hào)*,肯定是進(jìn)行數(shù)學(xué)運(yùn)算,所以操作數(shù)需轉(zhuǎn)化為number類型。
- 加號(hào)+,可能是字符串拼接,也可能是數(shù)學(xué)運(yùn)算,所以可能會(huì)轉(zhuǎn)化為number或string
- 一元運(yùn)算,如+[],只有一個(gè)操作數(shù)的,轉(zhuǎn)化為number類型
下面來看一下轉(zhuǎn)化規(guī)則。
1. 對(duì)于非原始類型的,通過ToPrimitive() 將值轉(zhuǎn)換成原始類型:
ToPrimitive(input, PreferredType?)
可選參數(shù)PreferredType是Number或者是String。返回值為任何原始值.如果PreferredType是Number,執(zhí)行順序如下:(參考:http://es5.github.io/#x9.1)
如果input為primitive,返回
否則,input為Object。調(diào)用 obj.valueOf()。如果結(jié)果是primitive,返回。
否則,調(diào)用obj.toString(). 如果結(jié)果是primitive,返回
否則,拋出TypeError
如果 PreferredType是String,步驟2跟3互換,如果PreferredType沒有,Date實(shí)例被設(shè)置成String,其他都是Number
2. 通過ToNumber()把值轉(zhuǎn)換成Number,直接看ECMA 9.3的表格http://es5.github.io/#x9.3
規(guī)則如下:
3. 通過ToString()把值轉(zhuǎn)化成字符串, 直接看ECMA 9.8的表格http://es5.github.io/#x9.8
規(guī)則就這么多,接下來實(shí)踐一下,根據(jù)我們上面劃分出的子表達(dá)式,一步一步將這個(gè)神奇的代碼給執(zhí)行出來。開工~
先看最簡單的子表達(dá)式16:+[]
只有一個(gè)操作數(shù)[],肯定是轉(zhuǎn)化為number了,根據(jù)上面的規(guī)則2,[]是個(gè)數(shù)組,object類型,即對(duì)象。所以得先調(diào)用toPrimitive轉(zhuǎn)化為原始類型,并且PreferredType為number,這個(gè)參數(shù)表示更“傾向于”轉(zhuǎn)化的類型,這里肯定是number了。然后首先調(diào)用數(shù)組的valueOf方法,數(shù)組調(diào)用valueOf會(huì)返回自身,如下:
- [].value.of()
- []
這個(gè)時(shí)候,我們得到一個(gè)空串“”,還沒有結(jié)束,看上面的規(guī)則2描述,繼續(xù)調(diào)用toNumber,轉(zhuǎn)化為number類型,如下:
- Number("")
- 0
大功告成!子表達(dá)式16轉(zhuǎn)化完畢,+[],最終得到0。
來看子表達(dá)式15:[~+""]
空串”"前面有兩個(gè)一元操作符,但是操作數(shù)還是只有一個(gè),所以,最終要轉(zhuǎn)化為的類型是number??匆?guī)則2吧,空串調(diào)用toNumber得到0。接下來是~,這是個(gè)什么東東呢?它是位運(yùn)算符,作用可以記為把數(shù)字取負(fù)然后減一,所以~0就是-1 。
別忘了,這個(gè)子表達(dá)式外頭還包著中括號(hào),所以最終的值為[-1],即一個(gè)數(shù)組,里面只有一個(gè)元素-1.
接下來看子表達(dá)式13就簡單了,把15、16求出來的填進(jìn)去,就變成了這樣:–[-1][0],取數(shù)組的第0個(gè)元素,然后自減,結(jié)果為-2,是不so easy!
繼續(xù)往上走,子表達(dá)式14: [~+[]]
其實(shí)把15、和16的原理用上就非常明顯了,答案[-1]
繼續(xù)來求子表達(dá)式9,此刻它已變成:-2*[-1],有稍許不一樣,不過沒關(guān)系,我們還是按照規(guī)則來,運(yùn)算符是乘號(hào)*,當(dāng)然是做數(shù)學(xué)運(yùn)算,那后面的[-1]就得轉(zhuǎn)化為number,與16的求法類似,過程如下:
①調(diào)用toPrimitive,發(fā)現(xiàn)是object類型
②調(diào)用valueOf,返回自身[-1]
③因?yàn)椴皇窃碱愋?,繼續(xù)調(diào)用toString,返回”-1″
④”-1″是原始類型了,然后調(diào)用toNumber,返回-1
⑤與-2相乘,返回2
子表達(dá)式10:~~!+[],不多說了,答案1. 就是從右往左依次一元計(jì)算。
有了9和10,我們來到了子表達(dá)式4,此刻它已經(jīng)長這樣了:2+1, 好,我不多說了。
繼續(xù)看表達(dá)式7:!(~+[]),~+[]=-1,這個(gè)根據(jù)上面已經(jīng)知道了,那!-1是什么呢?這里要說一下這個(gè)感嘆號(hào),它是邏輯取非的意思,會(huì)把表達(dá)式轉(zhuǎn)化為布爾類型,轉(zhuǎn)化規(guī)則和js的Truthy和Falsy原則是一樣的,后面跟數(shù)字的,除0以外都為false,后面跟字符串的,除空串以外都為false。這里的!-1當(dāng)然就是false了。
接下來這個(gè)表達(dá)式3:false+{}有點(diǎn)關(guān)鍵。一個(gè)布爾加一個(gè)對(duì)象,那這個(gè){}應(yīng)該先轉(zhuǎn)化為原始類型,流程如下:
①調(diào)用toPrimitive,發(fā)現(xiàn)是object類型
②調(diào)用valueOf,返回自身{},
③不是原始類型,調(diào)用toString,返回”[object Object]“
④false與”[object Object]“相加,false先轉(zhuǎn)化為字符串”false”
⑤相加得結(jié)果”false[object Object]“
知道了表達(dá)式3和4,我們就可以來看表達(dá)式1了,此時(shí)它是這樣的:”false[object Object]“[3],因?yàn)檫@個(gè)[]可以取字符串的子字符,像charAt一樣,所以得到了結(jié)果”s”
經(jīng)過上面艱難的流程,我們拿到了字符”s”,也就是那張圖的左半邊,剩下的那個(gè)”b”,相同的原理可以搞出來,我這里就不一一演示了,留給你練練吧~
回顧一下這個(gè)過程其實(shí)也不復(fù)雜,只是有一些需要重復(fù)勞動(dòng)的,只要你掌握了運(yùn)算的優(yōu)先級(jí),能把大串分解成一個(gè)個(gè)小串,然后運(yùn)用類型轉(zhuǎn)化的知識(shí)挨個(gè)處理就搞定了。怎么樣,看到這里你還覺得神奇嗎?
如果有人瞧不起js,請(qǐng)把這段代碼發(fā)給他,如果他想知道答案,請(qǐng)把本文發(fā)給他~