首頁圖來自看大圖剩瓶,侵刪延曙。
今天看到這樣一段代碼:
// JavaScript代碼
if ( !~items.indexOf( item ) ) {
items.push(item);
}
!~
是什么最新操作亡哄?于是花了一些時間查找了相關(guān)資料學(xué)習(xí)了一下。
先上干貨蚊惯,結(jié)論如下:
事實上截型,這是兩個運算,第一個運算是!
发钝,js中代表邏輯非
;第二個運算是~
酝豪,意義為按位非
。
上面那個例子蒲障,是在items
這個數(shù)組中查找元素item
的下標(biāo)瘫证,使用indexOf()
函數(shù)背捌。
ps: 其實不僅在js中有這種語法,其他任何語言也都會有。
- 如果在集合中查找到了item巡扇,則該函數(shù)返回對應(yīng)下標(biāo),是一個大于0的整數(shù),該整數(shù)按位非的結(jié)果一定不為0乖坠,取邏輯非后刀闷,表達式結(jié)果為假。
- 如果在集合中沒找到item顽分,則該函數(shù)返回
-1
這個值卒蘸。而恰好翻默,-1
這個值按位非的結(jié)果剛好是0,再取邏輯非后,表達式結(jié)果為真趾牧。
所以前面提到的代碼的含義為肯污,如果在items
中沒有item
這個元素吨枉,就添加到items
中东羹。
有貓病吧属提?
可能有些人看到這里就怒了美尸!欺負我智商不夠嗎?這種簡單的功能恕酸,我分分鐘就能寫出來好嗎胯陋?走你:
// 方法1
if ( items.indexOf( item ) === -1) {
items.push(item);
}
// 方法2
if ( tems.indexOf( item ) < 0) {
items.push(item);
}
扎心了,老鐵义矛,這三種方法實現(xiàn)的功能都是一樣的好嗎凉翻?那為什么還要使用這種晦澀難懂的語法呢捻激?我們可以揣摩一下這段代碼的作者的心里活動如下:
這種高級語法,其他人做得到嗎垃杖?韭赘??(叉會兒腰)
好吧脉漏,我們設(shè)想一下袖牙,也許是效率問題鞭达?這種語法更接近底層皇忿,所以執(zhí)行效率更快鳍烁?根據(jù)相關(guān)資料和測試繁扎,按位非
的寫法似乎也并沒有明顯的效率提升,反而還不如平時我們寫的邏輯判斷爹梁。ps:此處存疑提澎。
所以,如果是日常使用的話积糯,不知道~
是什么操作也完全OK絮宁。不想知道~
是什么的話服协,閣下可以關(guān)掉這個頁面了偿荷。
什么是按位非~
?
好吧唠椭,我們來剖析一下這里面的門道,看一下按位非
操作是什么樣子的寺庄。在深入之前力崇,我們需要先回顧一下相關(guān)知識。
原碼馍盟、反碼贞岭、補碼
來復(fù)習(xí)一下計算機基礎(chǔ)八毯,數(shù)字在計算機中是以二進制的形式存在的话速,那具體的存放規(guī)則又是什么呢?此處我們立一個大前提泊交,假設(shè)我們所處的環(huán)境是8位機活合,并且先只考慮整數(shù)的情況物赶。具體來看一下:
數(shù)字4用二進制表示是100
。由于是正數(shù)酵紫,首位字符位是0,所以補全位數(shù)是0000 0100
橄唬,這個就是數(shù)字4的原碼仰楚。由于正數(shù)的原碼補碼反碼都相等,所以數(shù)字4的反碼和補碼也是0000 0100
僧界。
如果是負數(shù)呢臭挽?比如數(shù)字-4
欢峰,應(yīng)先取正數(shù)的原碼,即0000 0100
宠漩,然后將首位(符號位)變?yōu)?,代表這是負數(shù)哄孤,所以我們得到了數(shù)字-4
的原碼是1000 0100
瘦陈。然后將除了首位(符號位)的其他位都取相反的值,得到反碼:1111 1011
晨逝,最后加上1,得到數(shù)字-4
的補碼:11111100
支鸡,所以我們得到以下這個表:
數(shù)字 | 4 | -4 |
---|---|---|
原碼 | 0000 0100 | 1000 0100 |
反碼 | 0000 0100 | 1111 1011 |
補碼 | 0000 0100 | 1111 1100 |
小結(jié)一下趁窃,原碼到補碼的步驟:
- 1, 原碼取反(除了首位)瀑构,得到反碼
- 2寺晌, 反碼+1,得到補碼
這個步驟一會兒還要用到呻征,先記一下陆赋。
而在計算機中嚷闭,為了運算簡便(只需要一套電路),數(shù)字的存儲都是存儲的補碼凌受,所以數(shù)字-4
在存儲單元中的值并不是它的原碼1000 0100
胜蛉,而是它的補碼誊册,即1111 1100
暖璧。同樣的,數(shù)字4
存放的也不是二進制0000 0100
嘲碱,而是它的補碼:0000 0100
(由于是正數(shù),所以這兩個值相同麦锯,但不應(yīng)該理解為單純地存儲二進制數(shù))扶欣。
~
運算
OK,現(xiàn)在我們有了理論的基礎(chǔ)料祠,我們再來討論按位非~
運算。
~
是一個單目運算符敛苇,它的定義是這樣的:
表達式中的任何一位為 1接谨,則結(jié)果中的該位變?yōu)?0。 表達式中的任何一位為 0脓豪,則結(jié)果中的該位變?yōu)?1忌卤。 --------摘自MSDN。
也就是說笤闯,~
運算的過程是這樣的棍厂,將要運算的數(shù)轉(zhuǎn)換為補碼牺弹,然后所有值為0的位變成1,值為1的位變?yōu)?晶默。即:
var m = ~3; // 對數(shù)字3執(zhí)行 按位非 運算
// 3 在計算機中存儲的值為 0000 0011
// 按位非之后航攒,變成了 1111 1100
// 請記住這是一個補碼,它代表的是十進制的數(shù)字 -4(上面的表格↑),所以m的值是 -4
console.log(m); // -4
那這個值怎么計算呢坞靶?我們再來一次圆丹,計算~25
的值:
var n = ~25; // 對數(shù)字25執(zhí)行 按位非 運算
// 25的補碼是 0001 1001
// 按位非之后,1110 0110
// 這個就是計算的結(jié)果硝枉,這個結(jié)果是一個補碼倦微。但是這個補碼怎么轉(zhuǎn)換為十進制呢欣福?我們可以將原碼到補碼的計算過程倒過來進行計算。只要通過這個補碼得到原碼雏逾,就知道十進制是多少了郑临。
// 補碼-1厢洞,得到反碼
// 反碼按位取反(除了首位),得到原碼
// 1110 0110 <-- 補碼
// 補碼減1 得到1110 0101 <-- 反碼
// 除了首位丧叽,其他位按位取反
// 得到1001 1010 <-- 原碼
// 觀察首位公你,為1陕靠,表示這是負數(shù),除去首位懦傍,剩下的二進制為 11010 ,即26
// 所以結(jié)果為 -26
console.log(n); // -26
另外粗俱,就像左移運算<<
虚吟、右移>>
等按位運算符在其他語言里一樣,~
運算在其他語言里也是可以使用的偏塞,使用方法完全相同。
~~
運算
由上面的例子擴展一下灸叼,如果是兩次取反古今,自然結(jié)果就變回來啦。特別要注意的是如果~
后面的表達式不是int值氓拼,而是bool值或者字符串或者其他值得話桃漾,計算機會把表達式強制轉(zhuǎn)換為int再計算拟逮。
也就是說唱歧,~~
會把后面的表達式強行變成int。
var n = ~~5; // 5
var m = ~~-8; // -8
var j = ~~true; // 將true轉(zhuǎn)換為int几于,也就是1沿彭,然后再計算尖滚。結(jié)果為1
!!
運算
講到這里不得不提一下以前經(jīng)常使用的!!
運算符漆弄,這個運算可以把表達式強行轉(zhuǎn)換為邏輯值,這個和上面提到的~~
類似廉邑。這種小技巧一樣適合其他語言。
if ( !!localStorage.getItem( "highScore" ) ){
localStorage.setItem( "highScore", "0" );
}
寫在最后
寫到這里特別想感慨一下,每個coder都有各自的編碼習(xí)慣蛛蒙,這種習(xí)慣一旦養(yǎng)成糙箍,想要改變是非常難的,所以希望大家在使用這些技巧之前牵祟,要考慮這種技巧的優(yōu)劣深夯,以免養(yǎng)成了不好的習(xí)慣,很難糾正掉诺苹。我用到這些奇巧淫技的地方是非常少的咕晋,理由很簡單,我不希望別人閱讀我的代碼非常吃力筝尾。像這種C語言風(fēng)格的代碼也許本就不應(yīng)該出現(xiàn)在js這種面向?qū)ο笳Z言中吧捡需。
閱讀別人的代碼也能夠看出別人的性格特點,如果別人寫出這樣的代碼給我閱讀筹淫,我可能會覺得這個人非常特立獨行。而我們公司也許并不需要這種特立獨行饰剥,作為研究學(xué)習(xí)尚可,但是實際應(yīng)用中顾孽,還是希望閱讀這篇文章的閣下,請避免寫出這樣的代碼蜒什。
所以我是反對過度使用奇巧淫技的霎冯。
啰嗦
由于在下水平有限,可能在文中有多處表達錯誤或不準確的地方缠俺。如果閣下在文章里發(fā)現(xiàn)在下寫的有失水準,還請不吝賜教墓卦,在評論指出。轉(zhuǎn)載請注明出處。如果希望鼓勵一下作者凡泣,點個贊就好鞋拟。