歡迎光臨
每天分享高質量文章

一行神奇的javascript代碼

寫本篇文章的緣由是之前群里@墨塵發了一段js代碼,如下:

(!(~+[])+{})[–[~+””][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]

然後讓大家運行,出來的結果讓人有點出乎意料,請看:

太風騷了有木有!如果有人詆毀前端瞧不起js的話,那就可以把這段代碼發給他了~

不過話說回來了,這到底是什麼原理呢?為什麼一堆符號運算結果竟然能是兩個字符,而且恰巧還是個sb!

其實靠的是js的型別轉化的一些基本原理,本篇就來揭密”sb”是如何煉成的。相信你如果能把這個理清楚了,以後遇到型別轉化之類的題目,就可以瞬間秒殺了。

首先要運用到的第一個知識就是js運算子的優先級,因為這麼長一段運算看的人眼花,我們必須得先根據優先級分成n小段,然後再各個擊破。優先級的排列如下表:

優先級從高到低:

根據此規則,我們把這一串運算分為以下16個子運算式:

運算子用紅色標出,有一點可能大家會意識不到,其實中括號[]也是一個運算子,用來通過索引訪問陣列項,另外也可以訪問字串的子字符,有點類似charAt方法,如:’abcd’[1] // 傳回’b’。而且中括號的優先級還是最高的哦。

預處理結束,接下來需要運用的就是javascript的型別轉化知識了。我們先說說什麼情況下需要進行型別轉化。當運算子兩邊的運算元型別不一致或者不是基本型別(也叫原始型別)時,需要進行型別轉化。先按運算子來分一下類:

  • 減號-,乘號*,肯定是進行數學運算,所以運算元需轉化為number型別。
  • 加號+,可能是字串拼接,也可能是數學運算,所以可能會轉化為number或string
  • 一元運算,如+[],只有一個運算元的,轉化為number型別

下麵來看一下轉化規則。

1. 對於非原始型別的,通過ToPrimitive() 將值轉換成原始型別:

ToPrimitive(input, PreferredType?)

可選引數PreferredType是Number或者是String。傳回值為任何原始值.如果PreferredType是Number,執行順序如下:(參考:http://es5.github.io/#x9.1)

如果input為primitive,傳回

否則,input為Object。呼叫 obj.valueOf()。如果結果是primitive,傳回。

否則,呼叫obj.toString(). 如果結果是primitive,傳回

否則,丟擲TypeError

如果 PreferredType是String,步驟2跟3互換,如果PreferredType沒有,Date實體被設置成String,其他都是Number

2. 通過ToNumber()把值轉換成Number,直接看ECMA 9.3的表格http://es5.github.io/#x9.3

規則如下:

3. 通過ToString()把值轉化成字串, 直接看ECMA 9.8的表格http://es5.github.io/#x9.8

規則就這麼多,接下來實踐一下,根據我們上面劃分出的子運算式,一步一步將這個神奇的代碼給執行出來。開工~

先看最簡單的子運算式16:+[]

只有一個運算元[],肯定是轉化為number了,根據上面的規則2,[]是個陣列,object型別,即物件。所以得先呼叫toPrimitive轉化為原始型別,並且PreferredType為number,這個引數表示更“傾向於”轉化的型別,這裡肯定是number了。然後首先呼叫陣列的valueOf方法,陣列呼叫valueOf會傳回自身,如下:

[].value.of()

[]

這個時候,我們得到一個空串“”,還沒有結束,看上面的規則2描述,繼續呼叫toNumber,轉化為number型別,如下:

Number(“”)

0

大功告成!子運算式16轉化完畢,+[],最終得到0。

來看子運算式15:[~+””]

空串””前面有兩個一元運算子,但是運算元還是只有一個,所以,最終要轉化為的型別是number。看規則2吧,空串呼叫toNumber得到0。接下來是~,這是個什麼東東呢?它是位運算子,作用可以記為把數字取負然後減一,所以~0就是-1 。

別忘了,這個子運算式外頭還包著中括號,所以最終的值為[-1],即一個陣列,裡面只有一個元素-1.

接下來看子運算式13就簡單了,把15、16求出來的填進去,就變成了這樣:–[-1][0],取陣列的第0個元素,然後自減,結果為-2,是不so easy!

繼續往上走,子運算式14: [~+[]]

其實把15、和16的原理用上就非常明顯了,答案[-1]

繼續來求子運算式9,此刻它已變成:-2*[-1],有稍許不一樣,不過沒關係,我們還是按照規則來,運算子是乘號*,當然是做數學運算,那後面的[-1]就得轉化為number,與16的求法類似,過程如下:

①呼叫toPrimitive,發現是object型別

②呼叫valueOf,傳回自身[-1]

③因為不是原始型別,繼續呼叫toString,傳回”-1″

④”-1″是原始型別了,然後呼叫toNumber,傳回-1

⑤與-2相乘,傳回2

子運算式10:~~!+[],不多說了,答案1. 就是從右往左依次一元計算。

有了9和10,我們來到了子運算式4,此刻它已經長這樣了:2+1, 好,我不多說了。

繼續看運算式7:!(~+[]),~+[]=-1,這個根據上面已經知道了,那!-1是什麼呢?這裡要說一下這個感嘆號,它是邏輯取非的意思,會把運算式轉化為布爾型別,轉化規則和js的Truthy和Falsy原則是一樣的,後面跟數字的,除0以外都為false,後面跟字串的,除空串以外都為false。這裡的!-1當然就是false了。

接下來這個運算式3:false+{}有點關鍵。一個布爾加一個物件,那這個{}應該先轉化為原始型別,流程如下:

①呼叫toPrimitive,發現是object型別

②呼叫valueOf,傳回自身{},

③不是原始型別,呼叫toString,傳回”[object Object]“

④false與”[object Object]“相加,false先轉化為字串”false”

⑤相加得結果”false[object Object]“

知道了運算式3和4,我們就可以來看運算式1了,此時它是這樣的:”false[object Object]“[3],因為這個[]可以取字串的子字符,像charAt一樣,所以得到了結果”s”

經過上面艱難的流程,我們拿到了字符”s”,也就是那張圖的左半邊,剩下的那個”b”,相同的原理可以搞出來,我這裡就不一一演示了,留給你練練吧~

回顧一下這個過程其實也不複雜,只是有一些需要重覆勞動的,只要你掌握了運算的優先級,能把大串分解成一個個小串,然後運用型別轉化的知識挨個處理就搞定了。怎麼樣,看到這裡你還覺得神奇嗎?

如果有人瞧不起js,請把這段代碼發給他,如果他想知道答案,請把本文發給他~

原文出處:呂大豹

http://www.cnblogs.com/lvdabao/p/4280518.html

赞(0)

分享創造快樂