程序員發了段代碼,被人拿着四十米大刀追了一條街!

程序員每天敲敲代碼,不會很枯燥嗎?是的,如果作爲一名程序員每天就只敲代碼,的確很乏味。但是誰說程序員每天就只會敲代碼呢?我們有我們的快樂,我們程序員騷起來,就沒你們的事了。

比如:

(!(+[])+{})[--[+""][+[]][~+[]] + ~!+[]]+({}+[])[[!+[]]~+[]]

如果你覺得某個人令你很不爽,於是,程序員就會發一段代碼,我們來看看結果:

哈哈,是不是被罵了還在問人這段代碼是什麼意思呢?

我來告訴你:

這段代碼爲什麼會輸出sb呢?其實這段代碼考的是js的類型轉化的一些基本原理。

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

1、js運算符的優先級

優先級的排列如下表,優先級從高到低:

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

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

接下來需要運用的就是java的類型轉化知識了,我們先說說什麼情況下需要進行類型轉化。當操作符兩邊的操作數類型不一致或者不是基本類型(也叫原始類型)時,需要進行類型轉化。

讓我們快速的複習一下,在Java中,一共有兩種類型的值:原始值(primitives)和對象值(objects)。

原始值有:undefined、null、布爾值(booleans)、數字(numbers)、還有字符串(strings)。

其他的所有值都是對象類型的值,包括數組(arrays)和函數(functions)。

2、類型轉化

先按運算符來分一下類:

減號-,乘號*,肯定是進行數學運算,所以操作數需轉化爲number類型。

加號+,可能是字符串拼接,也可能是數學運算,所以可能會轉化爲number或string。

一元運算,如+[],只有一個操作數的,轉化爲number類型。

下面來看一下轉化規則。

1、對於非原始類型的,通過

ToPrimitive()

將值轉換成原始類型

ToPrimitive(input, PreferredType?)

可選參數PreferredType是Number或者是String。返回值爲任何原始值.如果PreferredType是Number,執行順序如下:

如果input爲primitive,返回;

否則,input爲Object。調用

obj.valueOf()

。如果結果是primitive,返回;

否則,調用

obj.toString()

,如果結果是primitive,返回;

否則,拋出TypeError。

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

2、通過ToNumber()將值轉換爲數字

通過ToNumber()把值轉換成Number,直接看ECMA 9.3的表格:

如果輸入的值是一個對象,則會首先會調用

ToPrimitive(obj,Number)

將該對象轉換爲原始值,然後在調用ToNumber()將這個原始值轉換爲數字。

3、通過ToString()將值轉換爲字符串

通過ToString()把值轉化成字符串, 直接看ECMA 9.8的表格

如果輸入的值是一個對象,則會首先會調用

ToPrimitive(obj,String)

將該對象轉換爲原始值,然後再調用ToString()將這個原始值轉換爲字符串.

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

先看最簡單的子表達式16:

+[]

只有一個操作數[],肯定是轉化爲number了,根據上面的規則2,[]是個數組,object類型,即對象。所以得先調用toPrimitive轉化爲原始類型,並且PreferredType爲number,這個參數表示更“傾向於”轉化的類型,這裏肯定是number了。然後首先調用數組的valueOf方法,數組調用valueOf會返回自身,

這個時候,我們得到一個空串“”,還沒有結束,看上面的規則2描述,繼續調用toNumber,轉化爲number類型,

大功告成!子表達式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,返回

[objectObject]

false與

[objectObject]

相加,false先轉化爲字符串"false"

相加得結果

false[objectObject]

知道了表達式3和4,我們就可以來看表達式1了,此時它是這樣的:

false[objectObject]

[3],因爲這個[]可以取字符串的子字符,像charAt一樣,所以得到了結果"s"

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

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

同樣的,中文字符也是由這樣組成的,跟英文同樣的道理。

瞭解了嗎?如果以後有人給你發這段代碼,二話不說,平底鍋伺候。哈哈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章