首頁圖來自看大圖,侵刪。
今天看到這樣一段代碼:
// JavaScript代碼
if ( !~items.indexOf( item ) ) {
items.push(item);
}
!~
是什么最新操作?于是花了一些時間查找了相關資料學習了一下。
先上干貨,結論如下:
事實上,這是兩個運算,第一個運算是!
,js中代表邏輯非
;第二個運算是~
,意義為按位非
。
上面那個例子,是在items
這個數組中查找元素item
的下標,使用indexOf()
函數。
ps: 其實不僅在js中有這種語法,其他任何語言也都會有。
- 如果在集合中查找到了item,則該函數返回對應下標,是一個大于0的整數,該整數按位非的結果一定不為0,取邏輯非后,表達式結果為假。
- 如果在集合中沒找到item,則該函數返回
-1
這個值。而恰好,-1
這個值按位非的結果剛好是0,再取邏輯非后,表達式結果為真。
所以前面提到的代碼的含義為,如果在items
中沒有item
這個元素,就添加到items
中。
有貓病吧?
可能有些人看到這里就怒了!欺負我智商不夠嗎?這種簡單的功能,我分分鐘就能寫出來好嗎?走你:
// 方法1
if ( items.indexOf( item ) === -1) {
items.push(item);
}
// 方法2
if ( tems.indexOf( item ) < 0) {
items.push(item);
}
扎心了,老鐵,這三種方法實現的功能都是一樣的好嗎?那為什么還要使用這種晦澀難懂的語法呢?我們可以揣摩一下這段代碼的作者的心里活動如下:
這種高級語法,其他人做得到嗎???(叉會兒腰)
好吧,我們設想一下,也許是效率問題?這種語法更接近底層,所以執行效率更快?根據相關資料和測試,按位非
的寫法似乎也并沒有明顯的效率提升,反而還不如平時我們寫的邏輯判斷。ps:此處存疑。
所以,如果是日常使用的話,不知道~
是什么操作也完全OK。不想知道~
是什么的話,閣下可以關掉這個頁面了。
什么是按位非~
?
好吧,我們來剖析一下這里面的門道,看一下按位非
操作是什么樣子的。在深入之前,我們需要先回顧一下相關知識。
原碼、反碼、補碼
來復習一下計算機基礎,數字在計算機中是以二進制的形式存在的,那具體的存放規則又是什么呢?此處我們立一個大前提,假設我們所處的環境是8位機,并且先只考慮整數的情況。具體來看一下:
數字4用二進制表示是100
。由于是正數,首位字符位是0,所以補全位數是0000 0100
,這個就是數字4的原碼。由于正數的原碼補碼反碼都相等,所以數字4的反碼和補碼也是0000 0100
。
如果是負數呢?比如數字-4
,應先取正數的原碼,即0000 0100
,然后將首位(符號位)變為1,代表這是負數,所以我們得到了數字-4
的原碼是1000 0100
。然后將除了首位(符號位)的其他位都取相反的值,得到反碼:1111 1011
,最后加上1,得到數字-4
的補碼:11111100
,所以我們得到以下這個表:
數字 | 4 | -4 |
---|---|---|
原碼 | 0000 0100 | 1000 0100 |
反碼 | 0000 0100 | 1111 1011 |
補碼 | 0000 0100 | 1111 1100 |
小結一下,原碼到補碼的步驟:
- 1, 原碼取反(除了首位),得到反碼
- 2, 反碼+1,得到補碼
這個步驟一會兒還要用到,先記一下。
而在計算機中,為了運算簡便(只需要一套電路),數字的存儲都是存儲的補碼,所以數字-4
在存儲單元中的值并不是它的原碼1000 0100
,而是它的補碼,即1111 1100
。同樣的,數字4
存放的也不是二進制0000 0100
,而是它的補碼:0000 0100
(由于是正數,所以這兩個值相同,但不應該理解為單純地存儲二進制數)。
~
運算
OK,現在我們有了理論的基礎,我們再來討論按位非~
運算。
~
是一個單目運算符,它的定義是這樣的:
表達式中的任何一位為 1,則結果中的該位變為 0。 表達式中的任何一位為 0,則結果中的該位變為 1。 --------摘自MSDN。
也就是說,~
運算的過程是這樣的,將要運算的數轉換為補碼,然后所有值為0的位變成1,值為1的位變為0。即:
var m = ~3; // 對數字3執行 按位非 運算
// 3 在計算機中存儲的值為 0000 0011
// 按位非之后,變成了 1111 1100
// 請記住這是一個補碼,它代表的是十進制的數字 -4(上面的表格↑),所以m的值是 -4
console.log(m); // -4
那這個值怎么計算呢?我們再來一次,計算~25
的值:
var n = ~25; // 對數字25執行 按位非 運算
// 25的補碼是 0001 1001
// 按位非之后,1110 0110
// 這個就是計算的結果,這個結果是一個補碼。但是這個補碼怎么轉換為十進制呢?我們可以將原碼到補碼的計算過程倒過來進行計算。只要通過這個補碼得到原碼,就知道十進制是多少了。
// 補碼-1,得到反碼
// 反碼按位取反(除了首位),得到原碼
// 1110 0110 <-- 補碼
// 補碼減1 得到1110 0101 <-- 反碼
// 除了首位,其他位按位取反
// 得到1001 1010 <-- 原碼
// 觀察首位,為1,表示這是負數,除去首位,剩下的二進制為 11010 ,即26
// 所以結果為 -26
console.log(n); // -26
另外,就像左移運算<<
、右移>>
等按位運算符在其他語言里一樣,~
運算在其他語言里也是可以使用的,使用方法完全相同。
~~
運算
由上面的例子擴展一下,如果是兩次取反,自然結果就變回來啦。特別要注意的是如果~
后面的表達式不是int值,而是bool值或者字符串或者其他值得話,計算機會把表達式強制轉換為int再計算。
也就是說,~~
會把后面的表達式強行變成int。
var n = ~~5; // 5
var m = ~~-8; // -8
var j = ~~true; // 將true轉換為int,也就是1,然后再計算。結果為1
!!
運算
講到這里不得不提一下以前經常使用的!!
運算符,這個運算可以把表達式強行轉換為邏輯值,這個和上面提到的~~
類似。這種小技巧一樣適合其他語言。
if ( !!localStorage.getItem( "highScore" ) ){
localStorage.setItem( "highScore", "0" );
}
寫在最后
寫到這里特別想感慨一下,每個coder都有各自的編碼習慣,這種習慣一旦養成,想要改變是非常難的,所以希望大家在使用這些技巧之前,要考慮這種技巧的優劣,以免養成了不好的習慣,很難糾正掉。我用到這些奇巧淫技的地方是非常少的,理由很簡單,我不希望別人閱讀我的代碼非常吃力。像這種C語言風格的代碼也許本就不應該出現在js這種面向對象語言中吧。
閱讀別人的代碼也能夠看出別人的性格特點,如果別人寫出這樣的代碼給我閱讀,我可能會覺得這個人非常特立獨行。而我們公司也許并不需要這種特立獨行,作為研究學習尚可,但是實際應用中,還是希望閱讀這篇文章的閣下,請避免寫出這樣的代碼。
所以我是反對過度使用奇巧淫技的。
啰嗦
由于在下水平有限,可能在文中有多處表達錯誤或不準確的地方。如果閣下在文章里發現在下寫的有失水準,還請不吝賜教,在評論指出。轉載請注明出處。如果希望鼓勵一下作者,點個贊就好。